Twitch Auto Channel Points Claimer Redux

Automatically claim channel points with minimal performance impact.

  1. // ==UserScript==
  2. // @name Twitch Auto Channel Points Claimer Redux
  3. // @version 1.0.0
  4. // @author Jeffenson
  5. // @description Automatically claim channel points with minimal performance impact.
  6. // @match https://www.twitch.tv/*
  7. // @match https://dashboard.twitch.tv/*
  8. // @license MIT
  9. // @grant none
  10. // @namespace https://gf.qytechs.cn/users/983748
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. // Configuration options
  15. const config = {
  16. enableLogging: true,
  17. enableDebug: false,
  18. minDelay: 2000,
  19. maxAdditionalDelay: 1000,
  20. primaryCheckInterval: 3000, // Main interval for checking points (ms)
  21. fastCheckDuration: 10000, // Duration to use fast checking after page load (ms)
  22. fastCheckInterval: 1000, // Fast check interval during initial period (ms)
  23. observerMode: 'minimal', // 'none', 'minimal', or 'full'
  24. observerThrottleTime: 2000, // Minimum time between observer-triggered checks
  25. continuousOperation: true // Keep script running during navigation transitions
  26. };
  27.  
  28. // State variables
  29. let claiming = false;
  30. let observer = null;
  31. let checkInterval = null;
  32. let fastCheckInterval = null;
  33. let fastCheckTimeout = null;
  34. let urlCheckInterval = null;
  35. let statusInterval = null;
  36. let lastCheckTime = 0;
  37. let startTime = new Date();
  38. let instanceId = Math.random().toString(36).substring(2, 10);
  39.  
  40. // Track original history methods
  41. let originalPushState = null;
  42. let originalReplaceState = null;
  43.  
  44. // Debug statistics
  45. const stats = {
  46. intervalChecks: 0,
  47. observerChecks: 0,
  48. manualChecks: 0,
  49. bonusFound: 0,
  50. claimAttempts: 0,
  51. successfulClaims: 0,
  52. errors: 0,
  53. navigationEvents: 0,
  54. reinitializations: 0
  55. };
  56.  
  57. // Logging functions
  58. function log(message) {
  59. if (config.enableLogging) {
  60. console.log(`[Channel Points Claimer ${instanceId}] ${message}`);
  61. }
  62. }
  63.  
  64. function debug(message) {
  65. if (config.enableDebug) {
  66. console.debug(`[Channel Points Debug ${instanceId}] ${message}`);
  67. }
  68. }
  69.  
  70. function logStats() {
  71. if (config.enableDebug) {
  72. const runTime = Math.round((new Date() - startTime) / 1000);
  73. console.group(`Channel Points Claimer ${instanceId} - Debug Statistics`);
  74. console.log(`Runtime: ${runTime} seconds`);
  75. console.log(`Interval checks: ${stats.intervalChecks}`);
  76. console.log(`Observer-triggered checks: ${stats.observerChecks}`);
  77. console.log(`Manual checks: ${stats.manualChecks}`);
  78. console.log(`Bonus elements found: ${stats.bonusFound}`);
  79. console.log(`Claim attempts: ${stats.claimAttempts}`);
  80. console.log(`Successful claims: ${stats.successfulClaims}`);
  81. console.log(`Errors: ${stats.errors}`);
  82. console.log(`Navigation events: ${stats.navigationEvents}`);
  83. console.log(`Reinitializations: ${stats.reinitializations}`);
  84. console.log(`Current page: ${window.location.href}`);
  85. console.log(`Observer mode: ${config.observerMode}`);
  86. console.log(`Active intervals: ${checkInterval ? 'Main✓' : 'Main✗'} ${fastCheckInterval ? 'Fast✓' : 'Fast✗'} ${urlCheckInterval ? 'URL✓' : 'URL✗'}`);
  87. console.groupEnd();
  88. }
  89. }
  90.  
  91. // Check for and claim bonus
  92. function checkForBonus(source = 'interval') {
  93. // Track check source
  94. if (source === 'interval') stats.intervalChecks++;
  95. else if (source === 'observer') stats.observerChecks++;
  96. else if (source === 'manual') stats.manualChecks++;
  97.  
  98. // Throttle checks
  99. const now = Date.now();
  100. if (now - lastCheckTime < 500) { // Minimum 500ms between any checks
  101. return false;
  102. }
  103. lastCheckTime = now;
  104.  
  105. try {
  106. // More specific selector targeting
  107. const bonusSelectors = [
  108. '.claimable-bonus__icon',
  109. '[data-test-selector="community-points-claim"]',
  110. '.community-points-summary button[aria-label*="Claim"]',
  111. '.channel-points-reward-button',
  112. 'button[aria-label="Claim Bonus"]',
  113. 'button[data-a-target="chat-claim-bonus-button"]',
  114. // Add more selectors if Twitch changes their UI
  115. ];
  116.  
  117. let bonus = null;
  118. for (const selector of bonusSelectors) {
  119. const elements = document.querySelectorAll(selector);
  120. if (elements && elements.length > 0) {
  121. // Try to find the most visible/interactive element
  122. for (const element of elements) {
  123. if (element.offsetParent !== null && !element.disabled && element.style.display !== 'none') {
  124. bonus = element;
  125. debug(`Found bonus with selector: ${selector} (source: ${source})`);
  126. break;
  127. }
  128. }
  129. if (bonus) break;
  130. }
  131. }
  132.  
  133. if (bonus) {
  134. stats.bonusFound++;
  135.  
  136. if (!claiming) {
  137. stats.claimAttempts++;
  138. debug(`Attempting to claim bonus (attempt #${stats.claimAttempts})`);
  139.  
  140. try {
  141. bonus.click();
  142. const date = new Date().toLocaleTimeString();
  143. claiming = true;
  144.  
  145. // Random delay before allowing another claim
  146. const claimDelay = config.minDelay + (Math.random() * config.maxAdditionalDelay);
  147.  
  148. setTimeout(() => {
  149. stats.successfulClaims++;
  150. log(`Claimed at ${date} (total: ${stats.successfulClaims})`);
  151. claiming = false;
  152.  
  153. // After claiming, do a quick follow-up check in case there are multiple bonuses
  154. setTimeout(() => checkForBonus('follow-up'), 500);
  155. }, claimDelay);
  156.  
  157. return true;
  158. } catch (clickError) {
  159. stats.errors++;
  160. log(`Error clicking bonus: ${clickError.message}`);
  161. claiming = false;
  162. return false;
  163. }
  164. } else {
  165. debug('Bonus found but still in claiming cooldown');
  166. }
  167. }
  168. return false;
  169. } catch (error) {
  170. stats.errors++;
  171. log(`Error in checkForBonus: ${error.message}`);
  172. debug(`Stack trace: ${error.stack}`);
  173. return false;
  174. }
  175. }
  176.  
  177. // Set up the primary interval-based checking system
  178. function setupIntervalChecker() {
  179. debug('Setting up interval checkers');
  180.  
  181. // Clear any existing intervals
  182. if (checkInterval) {
  183. clearInterval(checkInterval);
  184. checkInterval = null;
  185. debug('Cleared existing main interval');
  186. }
  187.  
  188. if (fastCheckInterval) {
  189. clearInterval(fastCheckInterval);
  190. fastCheckInterval = null;
  191. debug('Cleared existing fast interval');
  192. }
  193.  
  194. if (fastCheckTimeout) {
  195. clearTimeout(fastCheckTimeout);
  196. fastCheckTimeout = null;
  197. debug('Cleared existing fast timeout');
  198. }
  199.  
  200. // Set up the main checking interval
  201. checkInterval = setInterval(() => {
  202. checkForBonus('interval');
  203. }, config.primaryCheckInterval);
  204.  
  205. debug(`Main interval checker set up with ${config.primaryCheckInterval}ms interval`);
  206.  
  207. // Initially use a faster check interval for a short period after page load
  208. fastCheckInterval = setInterval(() => {
  209. checkForBonus('fast-interval');
  210. }, config.fastCheckInterval);
  211.  
  212. debug(`Fast checking enabled with ${config.fastCheckInterval}ms interval`);
  213.  
  214. fastCheckTimeout = setTimeout(() => {
  215. if (fastCheckInterval) {
  216. clearInterval(fastCheckInterval);
  217. fastCheckInterval = null;
  218. debug('Fast checking period ended, cleared interval');
  219. }
  220. }, config.fastCheckDuration);
  221.  
  222. debug(`Fast checking will end after ${config.fastCheckDuration}ms`);
  223. }
  224.  
  225. // Set up a minimal observer that only triggers on very specific changes
  226. function setupMinimalObserver() {
  227. if (config.observerMode === 'none') {
  228. debug('Observer disabled by configuration');
  229. return;
  230. }
  231.  
  232. const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
  233. if (!MutationObserver) {
  234. log('MutationObserver not supported in this browser');
  235. return;
  236. }
  237.  
  238. // Clean up any existing observer
  239. if (observer) {
  240. observer.disconnect();
  241. observer = null;
  242. debug('Cleared existing points observer');
  243. }
  244.  
  245. observer = new MutationObserver(mutations => {
  246. // Only process if we see specific bonus-related changes
  247. const relevantChange = mutations.some(mutation => {
  248. // Check if this is a relevant element
  249. if (mutation.target && mutation.target.className &&
  250. /claimable|claim-button|bonus|points-reward/i.test(mutation.target.className)) {
  251. return true;
  252. }
  253.  
  254. // Check added nodes for bonus elements
  255. if (mutation.addedNodes && mutation.addedNodes.length) {
  256. for (const node of mutation.addedNodes) {
  257. if (node.nodeType === 1 && node.className &&
  258. /claimable|claim-button|bonus|points-reward/i.test(node.className)) {
  259. return true;
  260. }
  261. }
  262. }
  263.  
  264. return false;
  265. });
  266.  
  267. if (relevantChange) {
  268. debug('Detected relevant DOM change for channel points');
  269. checkForBonus('observer');
  270. }
  271. });
  272.  
  273. // Find the most specific target possible
  274. const pointsContainerSelectors = [
  275. '.community-points-summary',
  276. '.channel-points-container',
  277. '.chat-input__buttons-container',
  278. '.chat-input',
  279. '.chat-room'
  280. ];
  281.  
  282. let targetNode = null;
  283. for (const selector of pointsContainerSelectors) {
  284. const element = document.querySelector(selector);
  285. if (element) {
  286. targetNode = element;
  287. debug(`Found specific observer target: ${selector}`);
  288. break;
  289. }
  290. }
  291.  
  292. if (!targetNode) {
  293. if (config.observerMode === 'minimal') {
  294. debug('No specific target found for minimal observer, skipping observer setup');
  295. return;
  296. }
  297. targetNode = document.querySelector('.right-column') || document.body;
  298. debug(`Using fallback observer target: ${targetNode.tagName}`);
  299. }
  300.  
  301. // Very selective observation configuration
  302. const observerConfig = {
  303. childList: true,
  304. subtree: true,
  305. attributes: true,
  306. attributeFilter: ['class', 'data-test-selector', 'aria-label'],
  307. characterData: false
  308. };
  309.  
  310. observer.observe(targetNode, observerConfig);
  311. debug(`Observer set up in ${config.observerMode} mode on ${targetNode.tagName}`);
  312. }
  313.  
  314. // Restore original history methods
  315. function restoreHistoryMethods() {
  316. if (originalPushState) {
  317. history.pushState = originalPushState;
  318. debug('Restored original pushState');
  319. }
  320.  
  321. if (originalReplaceState) {
  322. history.replaceState = originalReplaceState;
  323. debug('Restored original replaceState');
  324. }
  325. }
  326.  
  327. // Complete cleanup function for page navigation
  328. function cleanup() {
  329. debug('Running cleanup');
  330.  
  331. if (observer) {
  332. observer.disconnect();
  333. observer = null;
  334. debug('Observer disconnected');
  335. }
  336.  
  337. if (checkInterval) {
  338. clearInterval(checkInterval);
  339. checkInterval = null;
  340. debug('Main interval checker stopped');
  341. }
  342.  
  343. if (fastCheckInterval) {
  344. clearInterval(fastCheckInterval);
  345. fastCheckInterval = null;
  346. debug('Fast interval checker stopped');
  347. }
  348.  
  349. if (fastCheckTimeout) {
  350. clearTimeout(fastCheckTimeout);
  351. fastCheckTimeout = null;
  352. debug('Fast check timeout cleared');
  353. }
  354.  
  355. if (!config.continuousOperation) {
  356. // Only clear URL check interval if not in continuous mode
  357. if (urlCheckInterval) {
  358. clearInterval(urlCheckInterval);
  359. urlCheckInterval = null;
  360. debug('URL check interval stopped');
  361. }
  362.  
  363. // Only clear status interval if not in continuous mode
  364. if (statusInterval) {
  365. clearInterval(statusInterval);
  366. statusInterval = null;
  367. debug('Status interval stopped');
  368. }
  369.  
  370. // Restore original history methods
  371. restoreHistoryMethods();
  372.  
  373. // Remove popstate listener
  374. window.removeEventListener('popstate', checkForUrlChange);
  375. }
  376.  
  377. logStats();
  378. }
  379.  
  380. // Function to check URL changes
  381. function checkForUrlChange() {
  382. const currentUrl = location.href;
  383. if (currentUrl !== window.lastTwitchUrl) {
  384. stats.navigationEvents++;
  385. debug(`URL changed from ${window.lastTwitchUrl} to ${currentUrl} (navigation #${stats.navigationEvents})`);
  386. window.lastTwitchUrl = currentUrl;
  387.  
  388. if (config.continuousOperation) {
  389. // In continuous mode, we keep the URL checker running
  390. // but reinitialize the points checkers
  391. if (checkInterval) {
  392. clearInterval(checkInterval);
  393. checkInterval = null;
  394. }
  395.  
  396. if (fastCheckInterval) {
  397. clearInterval(fastCheckInterval);
  398. fastCheckInterval = null;
  399. }
  400.  
  401. if (fastCheckTimeout) {
  402. clearTimeout(fastCheckTimeout);
  403. fastCheckTimeout = null;
  404. }
  405.  
  406. if (observer) {
  407. observer.disconnect();
  408. observer = null;
  409. }
  410.  
  411. // Do a final check before reinitializing
  412. checkForBonus('navigation');
  413.  
  414. // Reinitialize with delay
  415. debug('Waiting 1.5 seconds before re-initializing checkers');
  416. setTimeout(() => {
  417. debug('Re-initializing checkers after navigation');
  418. stats.reinitializations++;
  419. setupIntervalChecker();
  420. setupMinimalObserver();
  421. }, 1500);
  422. } else {
  423. // Complete cleanup in non-continuous mode
  424. cleanup();
  425.  
  426. // Reinitialize with delay
  427. debug('Waiting 1.5 seconds before re-initializing');
  428. setTimeout(() => {
  429. debug('Re-initializing after navigation');
  430. stats.reinitializations++;
  431. initialize();
  432. }, 1500);
  433. }
  434. }
  435. }
  436.  
  437. // Handle page navigation
  438. function setupPageListeners() {
  439. debug('Setting up page navigation listeners');
  440.  
  441. // Clean up when leaving the page
  442. window.addEventListener('beforeunload', () => {
  443. debug('Page unloading, cleaning up');
  444. cleanup();
  445. });
  446.  
  447. // Store initial URL in a global variable to avoid closure issues
  448. window.lastTwitchUrl = location.href;
  449. debug(`Initial URL: ${window.lastTwitchUrl}`);
  450.  
  451. // Clear any existing URL check interval
  452. if (urlCheckInterval) {
  453. clearInterval(urlCheckInterval);
  454. urlCheckInterval = null;
  455. debug('Cleared existing URL check interval');
  456. }
  457.  
  458. // Set up a dedicated interval for URL checking
  459. urlCheckInterval = setInterval(checkForUrlChange, 1000);
  460. debug('URL check interval set up');
  461.  
  462. // Store original history methods
  463. if (!originalPushState) {
  464. originalPushState = history.pushState;
  465. }
  466. if (!originalReplaceState) {
  467. originalReplaceState = history.replaceState;
  468. }
  469.  
  470. // Override history methods
  471. history.pushState = function() {
  472. originalPushState.apply(this, arguments);
  473. debug('History pushState detected');
  474. checkForUrlChange();
  475. };
  476.  
  477. history.replaceState = function() {
  478. originalReplaceState.apply(this, arguments);
  479. debug('History replaceState detected');
  480. checkForUrlChange();
  481. };
  482.  
  483. // And listen for popstate events
  484. window.removeEventListener('popstate', checkForUrlChange); // Remove any existing listener
  485. window.addEventListener('popstate', checkForUrlChange);
  486. }
  487.  
  488. // Periodic status reporting
  489. function setupStatusReporting() {
  490. const REPORT_INTERVAL = 60000; // 1 minute
  491.  
  492. if (statusInterval) {
  493. clearInterval(statusInterval);
  494. statusInterval = null;
  495. debug('Cleared existing status interval');
  496. }
  497.  
  498. statusInterval = setInterval(() => {
  499. debug('Periodic status check:');
  500.  
  501. // Check if we're on a channel page
  502. const onChannelPage = /twitch\.tv\/(?!directory|settings|u|p|user|videos|subscriptions|inventory|wallet)/.test(window.location.href);
  503. debug(`Current URL: ${window.location.href} (on channel page: ${onChannelPage})`);
  504.  
  505. // Check for channel points elements
  506. const pointsContainer = document.querySelector('.community-points-summary, .channel-points-container');
  507. debug(`Points container present: ${!!pointsContainer}`);
  508.  
  509. // Check if intervals are still running
  510. debug(`Active intervals: ${checkInterval ? 'Main✓' : 'Main✗'} ${fastCheckInterval ? 'Fast✓' : 'Fast✗'} ${urlCheckInterval ? 'URL✓' : 'URL✗'}`);
  511.  
  512. // Log full stats
  513. logStats();
  514.  
  515. // Do a manual check just to be safe
  516. checkForBonus('periodic');
  517. }, REPORT_INTERVAL);
  518.  
  519. debug('Status reporting set up');
  520. }
  521.  
  522. // Initialize everything
  523. function initialize() {
  524. log('Script starting');
  525. debug(`Version 2.1.0 - Final Release Version`);
  526. debug(`Instance ID: ${instanceId}`);
  527. debug(`User agent: ${navigator.userAgent}`);
  528. debug(`Current URL: ${window.location.href}`);
  529.  
  530. startTime = new Date();
  531. lastCheckTime = 0;
  532.  
  533. // First do an immediate check
  534. setTimeout(() => {
  535. debug('Running initial check');
  536. checkForBonus('initial');
  537. }, 1000);
  538.  
  539. // Set up the primary interval-based system
  540. setupIntervalChecker();
  541.  
  542. // Set up the minimal observer if enabled
  543. setupMinimalObserver();
  544.  
  545. // Set up page navigation listeners
  546. setupPageListeners();
  547.  
  548. // Set up status reporting
  549. setupStatusReporting();
  550. }
  551.  
  552. // Start the script
  553. initialize();
  554.  
  555. // Expose debug controls to console
  556. window.channelPointsDebug = {
  557. config: config,
  558. stats: stats,
  559. logStats: logStats,
  560. checkNow: () => {
  561. const result = checkForBonus('manual');
  562. return result ? "Bonus found and claimed!" : "No bonus available at this time";
  563. },
  564. reinitialize: () => {
  565. cleanup();
  566. initialize();
  567. return "Script reinitialized";
  568. },
  569. toggleDebug: () => {
  570. config.enableDebug = !config.enableDebug;
  571. log(`Debug mode ${config.enableDebug ? 'enabled' : 'disabled'}`);
  572. return config.enableDebug;
  573. },
  574. setObserverMode: (mode) => {
  575. if (['none', 'minimal', 'full'].includes(mode)) {
  576. config.observerMode = mode;
  577. cleanup();
  578. initialize();
  579. return `Observer mode set to ${mode}`;
  580. }
  581. return `Invalid mode. Use 'none', 'minimal', or 'full'`;
  582. },
  583. toggleContinuousOperation: () => {
  584. config.continuousOperation = !config.continuousOperation;
  585. log(`Continuous operation ${config.continuousOperation ? 'enabled' : 'disabled'}`);
  586. return config.continuousOperation;
  587. },
  588. instanceId: instanceId,
  589. cleanup: () => {
  590. cleanup();
  591. return "Manual cleanup completed";
  592. }
  593. };
  594.  
  595. debug('Debug controls available via window.channelPointsDebug');
  596. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址