Greasy Fork is available in English.

Rust Twitch Drop bot

Twitch Auto Claim, Drop, change channel and auto track progress


ติดตั้งสคริปต์นี้?
// ==UserScript==// @name         Rust Twitch Drop bot// @namespace    http://tampermonkey.net/// @version      2.7// @description  Twitch Auto Claim, Drop, change channel and auto track progress// @author       gig4d3v// @match        https://www.twitch.tv/drops/inventory// @grant        GM_addStyle// @grant        GM_xmlhttpRequest// @grant        GM_addElement// @require      https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js// @require      https://code.jquery.com/jquery-3.6.0.min.js// @require      https://code.jquery.com/ui/1.12.1/jquery-ui.min.js// @license      GPLv3// ==/UserScript==(function () {"use strict";const DEFAULT_CONFIG = {checkDropsInterval: 60000,checkStreamerStatusInterval: 20000,updateStreamerOnlineStatusInterval: 20000,pageRefreshInterval: 3600000,watchdogInterval: 60000,retryInterval: 2000,maxRetries: 5,elementTimeout: 5000,tabSwitchDelay: 5000,refreshCooldown: 300000,selectedCampaign: null,availableCampaigns: [],muteIframe: "true",iframeQuality: "low",};const CONFIG =JSON.parse(localStorage.getItem("twitchDropsManagerConfig")) ||DEFAULT_CONFIG;let allOnlineStreamersHaveAllItems = false;let streamers = [];let currentStreamerIndex = 0;let lastActivityTimestamp = Date.now();let startTime = Date.now();let consecutiveRefreshCount = 0;const maxConsecutiveRefreshes = 5;let initialDataLoaded = false;let lastRefreshTimestamp = 0;let actionPill;const StateHelper = {state: new Set(),setState(action) {this.state.add(action);this.updateActionPill();},clearState(action) {this.state.delete(action);this.updateActionPill();},updateActionPill() {const states = {initializing: "Initializing...",fetching: "Fetching Data...",streaming: "Streaming...",checkingDrops: "Checking Drops...",updatingStatus: "Updating Status...",refreshing: "Refreshing Page...",error: "Error",idle: "Idle",};const activeStates =Array.from(this.state).map((state) => states[state] || state).join(" | ") || "Idle";actionPill.text(activeStates);},};function saveConfig() {localStorage.setItem("twitchDropsManagerConfig", JSON.stringify(CONFIG));}function resetToDefaults() {Object.assign(CONFIG, DEFAULT_CONFIG);saveConfig();refreshPage();}function saveLog(message) {const logs = JSON.parse(localStorage.getItem("twitchDropsLogs")) || [];const timestamp = new Date().toISOString();logs.push({ timestamp, message });if (logs.length > 100) {logs.shift();}localStorage.setItem("twitchDropsLogs", JSON.stringify(logs));}function applyStyles() {GM_addStyle(`@import url('https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.2.19/tailwind.min.css');.tab-content::-webkit-scrollbar {width: 5px !important;}.tab-content::-webkit-scrollbar-track {background-color: #ebebeb !important;-webkit-border-radius: 10px !important;border-radius: 10px !important;}.tab-content::-webkit-scrollbar-thumb {-webkit-border-radius: 10px !important;border-radius: 10px !important;background: #6d6d6d !important;}`);}function createComponent(tag, className, content, attrs = {}) {const element = $(`<${tag} class="${className} custom-scrollbar">${content}</${tag}>`);for (const [key, value] of Object.entries(attrs)) {element.attr(key, value);}return element;}function centerPopup(popup) {const winWidth = $(window).width();const winHeight = $(window).height();const popupWidth = popup.outerWidth();const popupHeight = popup.outerHeight();const left = (winWidth - popupWidth) / 2;const top = (winHeight - popupHeight) / 2;popup.css({ left: `${left}px`, top: `${top}px` });}function createPopup(id, title, content) {const popup = createComponent("div","fixed z-50 bg-gray-800 text-white rounded-lg shadow-lg custom-scrollbar","",{id,style: "display: none; min-width: 300px; position: fixed !important;",});const header = createComponent("div","popup-header bg-gray-900 p-2 rounded-t-lg flex justify-between items-center cursor-move",`<span class="text-xl font-bold">${title}</span><button class="close-popup bg-red-600 text-white px-2 rounded">X</button>`);const body = createComponent("div", "popup-content p-2", content);popup.append(header).append(body);$("body").append(popup);popup.draggable({ handle: ".popup-header" }).resizable();header.find(".close-popup").on("click", () => popup.hide());centerPopup(popup);return popup;}function createMainPopup() {const content = `<ul class="tabs flex space-x-2"><li class="tab active p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#streamer-list-content">Streamer List</li><li class="tab p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#inventory-logs-content">Inventory Logs</li><li class="tab p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#online-status-logs-content">Online Status Logs</li><li class="tab p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#streamer-logs-content">Streamer Logs</li><li class="tab p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#global-logs-content">Global Logs</li><li class="tab p-2 cursor-pointer bg-gray-800 rounded-t-lg" data-target="#config-content">Config</li></ul><div class="tab-content p-4 bg-gray-800 rounded-b-lg text-lg overflow-y-scroll custom-scrollbar" style='width: 700px; height: 340px;'><div id="streamer-list-content" class="tab-pane active"><p class="text-lg font-bold mb-2">Current Streamer: <span id="current-streamer" class="font-normal"></span></p><ul id="streamer-list" class="list-disc pl-5 space-y-1 custom-scrollbar"></ul></div><div id="inventory-logs-content" class="tab-pane hidden"><p class="text-lg font-bold mb-2">Inventory Logs:</p><ul id="inventory-logs-list" class="list-disc pl-5 space-y-1 custom-scrollbar"></ul></div><div id="online-status-logs-content" class="tab-pane hidden"><p class="text-lg font-bold mb-2">Online Status Logs:</p><ul id="online-status-logs-list" class="list-disc pl-5 space-y-1 custom-scrollbar"></ul></div><div id="streamer-logs-content" class="tab-pane hidden"><p class="text-lg font-bold mb-2">Streamer Logs:</p><ul id="streamer-logs-list" class="list-disc pl-5 space-y-1 custom-scrollbar"></ul></div><div id="global-logs-content" class="tab-pane hidden"><p class="text-lg font-bold mb-2">Global Logs:</p><ul id="global-logs-list" class="list-disc pl-5 space-y-1 custom-scrollbar"></ul></div><div id="config-content" class="tab-pane hidden"><p class="text-lg font-bold mb-2">Configuration:</p><label class="block mb-2"><span>Check Drops Interval (ms):</span><input type="number" id="check-drops-interval" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.checkDropsInterval}"></label><label class="block mb-2"><span>Check Streamer Status Interval (ms):</span><input type="number" id="check-streamer-status-interval" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.checkStreamerStatusInterval}"></label><label class="block mb-2"><span>Update Streamer Online Status Interval (ms):</span><input type="number" id="update-streamer-online-status-interval" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.updateStreamerOnlineStatusInterval}"></label><label class="block mb-2"><span>Page Refresh Interval (ms):</span><input type="number" id="page-refresh-interval" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.pageRefreshInterval}"></label><label class="block mb-2"><span>Watchdog Timer Interval (ms):</span><input type="number" id="watchdog-interval" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.watchdogInterval}"></label><label class="block mb-2"><span>Element Timeout (ms):</span><input type="number" id="element-timeout" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.elementTimeout}"></label><label class="block mb-2"><span>Tab Switch Delay (ms):</span><input type="number" id="tab-switch-delay" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.tabSwitchDelay}"></label><label class="block mb-2"><span>Refresh Cooldown (ms):</span><input type="number" id="refresh-cooldown" class="bg-gray-700 text-white p-2 rounded w-full" value="${CONFIG.refreshCooldown}"></label><label class="block mb-2"><span>Mute Iframe:</span><select id="mute-iframe" class="bg-gray-700 text-white p-2 rounded w-full"><option value="true" ${CONFIG.muteIframe === "true" ? "selected" : ""}>Yes</option><option value="false" ${CONFIG.muteIframe === "false" ? "selected" : ""}>No</option></select></label><label class="block mb-2"><span>Iframe Quality:</span><select id="iframe-quality" class="bg-gray-700 text-white p-2 rounded w-full"><option value="low" ${CONFIG.iframeQuality === "low" ? "selected" : ""}>Low</option><option value="medium" ${CONFIG.iframeQuality === "medium" ? "selected" : ""}>Medium</option><option value="high" ${CONFIG.iframeQuality === "high" ? "selected" : ""}>High</option></select></label><label class="block mb-2"><span>Selected Campaign:</span><select id="selected-campaign" class="bg-gray-700 text-white p-2 rounded w-full">${CONFIG.availableCampaigns.map((campaign) =>`<option value="${campaign}" ${CONFIG.selectedCampaign === campaign ? "selected" : ""}>${campaign}</option>`).join("")}</select></label><button id="save-config" class="bg-green-600 text-white px-4 py-2 rounded">Save</button><button id="reset-config" class="bg-red-600 text-white px-4 py-2 rounded mt-2">Reset to Defaults</button></div></div>`;return createPopup("info-popup", "Twitch Drops Manager", content);}function createCampaignSelectionPopup() {const content = `<select id="campaign-select" class="bg-gray-700 text-white p-2 rounded w-full"></select><button id="select-campaign-button" class="bg-green-600 text-white px-4 py-2 rounded mt-2 w-full">Select</button>`;return createPopup("campaign-selection-popup","Select a Campaign",content);}function createStreamerFrame() {const container = createComponent("div","fixed z-50 bg-gray-800 rounded-lg shadow-lg draggable resizable custom-scrollbar","",{id: "streamer-frame-container",style:"display: none; width: fit-content; height: fit-content; position: fixed !important;",});const header = createComponent("div","popup-header bg-gray-900 p-2 rounded-t-lg flex justify-between items-center cursor-move",`<span class="text-xl font-bold" id="streamer-title">Streamer Window</span><button id="minimize-streamer" class="bg-blue-600 text-white px-2 rounded">-</button>`);const iframe = createComponent("iframe", "", "", {id: "streamer-frame",src: "https://www.kcchanphotography.com/resources/website/common/images/loading-spin.svg",style: "width: 700px; height: 500px",});container.append(header).append(iframe);$("body").append(container);container.draggable({ handle: ".popup-header" }).resizable();header.find("#minimize-streamer").on("click", () => {const minimized = container.hasClass("minimized");container.toggleClass("minimized", !minimized);iframe.toggle(minimized);header.find("#minimize-streamer").text(minimized ? "+" : "-");});centerPopup(container);return container;}function createActionButton() {const button = createComponent("button","fixed bottom-4 right-4 bg-blue-600 text-white p-2 rounded shadow-lg z-50 custom-scrollbar","Twitch Drops Manager");actionPill = createComponent("span","fixed bottom-4 left-4 bg-gray-700 text-white p-2 rounded shadow-lg z-50 custom-scrollbar","Initializing...");button.on("click", () => $("#info-popup").toggle());$("body").append(button).append(actionPill);return actionPill;}function addEventListeners() {$(document).on("click", ".tab", function () {$(".tab").removeClass("active");$(this).addClass("active");$(".tab-pane").removeClass("active").addClass("hidden");$($(this).data("target")).removeClass("hidden").addClass("active");});$("#save-config").on("click", () => {CONFIG.checkDropsInterval = parseInt($("#check-drops-interval").val(),10);CONFIG.checkStreamerStatusInterval = parseInt($("#check-streamer-status-interval").val(),10);CONFIG.updateStreamerOnlineStatusInterval = parseInt($("#update-streamer-online-status-interval").val(),10);CONFIG.pageRefreshInterval = parseInt($("#page-refresh-interval").val(),10);CONFIG.watchdogInterval = parseInt($("#watchdog-interval").val(), 10);CONFIG.elementTimeout = parseInt($("#element-timeout").val(), 10);CONFIG.tabSwitchDelay = parseInt($("#tab-switch-delay").val(), 10);CONFIG.refreshCooldown = parseInt($("#refresh-cooldown").val(), 10);CONFIG.muteIframe = $("#mute-iframe").val();CONFIG.iframeQuality = $("#iframe-quality").val();CONFIG.selectedCampaign = $("#selected-campaign").val();saveConfig();alert("Configuration saved!");refreshPage();});$("#reset-config").on("click", () => {if (confirm("Are you sure you want to reset to defaults?")) {resetToDefaults();}});$("#select-campaign-button").on("click", () => {const selectedCampaign = $("#campaign-select").val();if (selectedCampaign) {CONFIG.selectedCampaign = selectedCampaign;CONFIG.availableCampaigns = $("#campaign-select option").map((_, option) => option.value).toArray();saveConfig();$("#campaign-selection-popup").hide();refreshPage();}});}function addLog(containerId, message) {const logsListElement = $(`#${containerId}`);const logItem = createComponent("li", "", message);logsListElement.append(logItem);saveLog(message);if (logsListElement.children().length > 100) {logsListElement.children().first().remove();}}function loadGlobalLogs() {const logs = JSON.parse(localStorage.getItem("twitchDropsLogs")) || [];const logsListElement = $("#global-logs-list");logsListElement.empty();logs.forEach((log) => {const logItem = createComponent("li","",`${log.timestamp}: ${log.message}`);logsListElement.append(logItem);});}function retryFetch(fetchFunction,maxRetries = CONFIG.maxRetries,interval = CONFIG.retryInterval) {return new Promise((resolve, reject) => {let attempts = 0;const executeFetch = async () => {try {const r###lt = await fetchFunction();resolve(r###lt);} catch (error) {if (attempts < maxRetries) {attempts++;setTimeout(executeFetch, interval);} else {reject(error);}}};executeFetch();});}function timeoutPromise(ms, promise) {return new Promise((resolve, reject) => {const timer = setTimeout(() => {reject(new Error("Request timed out"));}, ms);promise.then((value) => {clearTimeout(timer);resolve(value);}).catch((error) => {clearTimeout(timer);reject(error);});});}async function getStreamerOnlineStatus(streamerNames) {try {const streamerStatuses = {};const fetchStatus = (name) => {return new Promise((resolve, reject) => {GM_xmlhttpRequest({method: "GET",url: `https://www.twitch.tv/${name}`,onload: (response) => {const parser = new DOMParser();const doc = parser.parseFromString(response.responseText,"text/html");const scripts = doc.querySelectorAll("script");let isLive = false;scripts.forEach((script) => {if (script.textContent.includes("isLiveBroadcast")) {isLive = true;}});streamerStatuses[name] = isLive;resolve();},onerror: () => {streamerStatuses[name] = false;reject(new Error(`Failed to fetch status for ${name}`));},});});};await Promise.all(streamerNames.map((name) =>retryFetch(() =>timeoutPromise(CONFIG.elementTimeout, fetchStatus(name)))));return streamerStatuses;} catch (error) {addLog("online-status-logs-list",`Error fetching streamer statuses: ${error}`);refreshPage();throw error;}}async function switchTabs(tabName) {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const tabList = document.querySelectorAll('[role="tablist"]')[0];if (tabList) {const tabs = tabList.children;for (let i = 0; i < tabs.length; i++) {if (tabs[i].textContent.trim() === tabName) {tabs[i].children[0].click();setTimeout(resolve, CONFIG.tabSwitchDelay);return;}}}reject(new Error(`Tab ${tabName} not found`));} catch (error) {reject(error);}})));}async function getInventoryData() {try {await switchTabs("All Campaigns");await switchTabs("Inventory");addLog("inventory-logs-list", "Reloaded inventory progress.");setTimeout(async () => {addLog("inventory-logs-list", "Checking for claim button...");const claimButton = await getClaimButton();if (claimButton) {claimButton.click();addLog("inventory-logs-list", "Claimed a drop.");}}, CONFIG.tabSwitchDelay);} catch (error) {addLog("inventory-logs-list", `Error getting inventory data: ${error}`);refreshPage();}}function getClaimButton() {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const xpathExpression = "//div[text()='Claim Now']";const r###lt = document.evaluate(xpathExpression,document,null,XPathR###lt.ANY_TYPE,null);const divElement = r###lt.iterateNext();let grandparentElement = null;if (divElement) {const parentElement = divElement.parentNode;grandparentElement = parentElement.parentNode;resolve(grandparentElement);} else {reject(new Error("Claim button not found"));}} catch (error) {reject(error);}})));}function getCampaigns() {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const cleanArray = (arr) => {const filteredArray = arr.filter((item) =>item !== null && item !== undefined && !Number.isNaN(item));const uniqueArray = [...new Set(filteredArray)];return uniqueArray;};const aTags = document.getElementsByTagName("div");let found = [];const r###lt = [];for (let i = 0; i < aTags.length; i++) {if (aTags[i].textContent === "Watch to Redeem") {found.push(aTags[i].parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement.children[0].querySelector("h3.tw-title")?.textContent);}}if (found.length > 0) {resolve(cleanArray(found));} else {reject(new Error("No campaigns found"));}} catch (error) {reject(error);}})));}function getCampaignData() {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const aTags = document.getElementsByTagName("h3");let found;const r###lt = [];for (let i = 0; i < aTags.length; i++) {if (aTags[i].textContent === CONFIG.selectedCampaign) {found = aTags[i];break;}}if (found) {const mainContainer =found.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;mainContainer.querySelectorAll("a").forEach((streamer) => {const container =streamer.parentElement.parentElement.parentElement.parentElement.parentElement.parentElement;if (container.children[0].children[0].textContent ==="How to Earn the Drop") {const name =streamer.textContent === "a participating live channel"? "general": streamer.textContent.toLowerCase();const items =container.parentElement.children[1].children[1].children[0].querySelectorAll("img").length;const itemNames = [];container.parentElement.children[1].children[1].children[0].querySelectorAll("img").forEach((imgEl) => {itemNames.push(imgEl.parentElement.parentElement.parentElement.children[1].children[0].children[0].textContent);});r###lt.push({ name, items, itemNames });}});}if (r###lt.length > 0) {resolve(r###lt);} else {reject(new Error("No campaigns found"));}} catch (error) {reject(error);}})));}function getClaimedItemsNamesInv() {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const aTags = document.getElementsByTagName("h5");let found;const r###lt = [];for (let i = 0; i < aTags.length; i++) {if (aTags[i].textContent === "Claimed") {found = aTags[i];break;}}if (found) {const itemImgs =found.parentElement.parentElement.parentElement.children[1].querySelectorAll("img");itemImgs.forEach((imgEl) => {r###lt.push(imgEl.parentElement.parentElement.parentElement.children[1].children[1].children[0].textContent);});resolve(r###lt);} else {reject(new Error("Claimed items not found"));}} catch (error) {reject(error);}})));}function switchToTab(tabName) {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const tabList = document.querySelectorAll('[role="tablist"]')[0];if (tabList) {const tabs = tabList.children;for (let i = 0; i < tabs.length; i++) {if (tabs[i].textContent.trim() === tabName) {tabs[i].children[0].click();setTimeout(resolve, CONFIG.tabSwitchDelay);return;}}}reject(new Error(`Tab ${tabName} not found`));} catch (error) {reject(error);}})));}async function updateInfoPanel() {return retryFetch(() =>timeoutPromise(CONFIG.elementTimeout,new Promise((resolve, reject) => {try {const currentStreamer = streamers[currentStreamerIndex];if (!currentStreamer) {throw new Error("Current streamer not found");}$("#current-streamer").text(currentStreamer.name);const streamerListElement = $("#streamer-list");streamerListElement.empty();streamers.forEach((streamer) => {const missingItems = streamer.itemNames? streamer.itemNames.filter((item) => !streamer.claimedItems.includes(item)): [];const listItem = createComponent("li","",`${streamer.name}: ${streamer.online ? "Online" : "Offline"} - ${streamer.claimedItems.length}/${streamer.allItems} - Missing Items: ${missingItems.length ? missingItems.join(", ") : "none"}`);streamerListElement.append(listItem);});const streamerTitle = `${currentStreamer.name} - ${currentStreamer.online ? "Online" : "Offline"} - ${currentStreamer.claimedItems.length}/${currentStreamer.allItems}`;$("#streamer-title").text(streamerTitle);resolve();} catch (error) {reject(error);}})));}async function initStreamers() {try {await getInitialDataFromCampaigns();const streamerNames = streamers.map((s) => s.name);const streamerData = await getStreamerOnlineStatus(streamerNames);streamers.forEach((streamer) => {streamer.online = streamerData[streamer.name];});initialDataLoaded = true;} catch (error) {addLog("inventory-logs-list", `Error initializing streamers: ${error}`);refreshPage();}}async function getInitialDataFromCampaigns() {try {await switchToTab("All Campaigns");return new Promise((resolve) =>setTimeout(async () => {try {const campaignData = await getCampaignData();if (!campaignData ||!Array.isArray(campaignData) ||campaignData.length === 0 ||campaignData.some((data) => !data || !data.name || !data.items || !data.itemNames)) {addLog("inventory-logs-list","Campaign data invalid or empty, refreshing page.");refreshPage();return;}addLog("inventory-logs-list",`Campaign data retrieved: ${JSON.stringify(campaignData)}`);streamers = campaignData.map((data) => ({name: data.name.replace(/^\//, ""),online: false,allItems: data.items,itemNames: data.itemNames,claimedItems: [],}));resolve();} catch (error) {addLog("inventory-logs-list",`Error getting campaign data: ${error}`);refreshPage();}}, CONFIG.tabSwitchDelay));} catch (error) {addLog("inventory-logs-list",`Error getting initial data from campaigns: ${error}`);refreshPage();}}async function checkDropsAndUpdateStreamers() {try {StateHelper.setState("checkingDrops");await switchToTab("Inventory");return new Promise((resolve) =>setTimeout(async () => {try {await getInventoryData();const claimedItems = await getClaimedItemsNamesInv();addLog("inventory-logs-list",`Claimed items retrieved: ${JSON.stringify(claimedItems)}`);streamers.forEach((streamer) => {streamer.claimedItems = claimedItems.filter((item) =>streamer.itemNames.includes(item));});await updateInfoPanel();resolve();} catch (error) {addLog("inventory-logs-list",`Error checking drops and updating streamers: ${error}`);refreshPage();} finally {StateHelper.clearState("checkingDrops");}}, CONFIG.tabSwitchDelay));} catch (error) {addLog("inventory-logs-list",`Error checking drops and updating streamers: ${error}`);refreshPage();}}async function checkStreamerStatus() {try {StateHelper.setState("updatingStatus");const currentStreamer = streamers[currentStreamerIndex];if (!currentStreamer) {throw new Error("Current streamer not found");}allOnlineStreamersHaveAllItems = streamers.filter((s) => s.online).every((s) => s.allItems === s.claimedItems.length);if ((!currentStreamer.online ||currentStreamer.claimedItems.length === currentStreamer.allItems) &&!allOnlineStreamersHaveAllItems) {let nextStreamerFound = false;for (let i = 0; i < streamers.length; i++) {currentStreamerIndex = (currentStreamerIndex + 1) % streamers.length;const nextStreamer = streamers[currentStreamerIndex];if (nextStreamer &&nextStreamer.allItems > nextStreamer.claimedItems.length) {nextStreamerFound = true;break;}}if (nextStreamerFound) {await updateInfoPanel();setIframeSrc(streamers[currentStreamerIndex].name);addLog("streamer-logs-list",`Switched to next streamer: ${streamers[currentStreamerIndex].name}`);} else {addLog("streamer-logs-list","No more streamers with available drops.");}} else if (!currentStreamer.online && allOnlineStreamersHaveAllItems) {addLog("streamer-logs-list", "All streamers have all items.");let nextStreamerFound = false;for (let i = 0; i < streamers.length; i++) {currentStreamerIndex = Math.floor(Math.random() * streamers.length);const nextStreamer = streamers[currentStreamerIndex];if (nextStreamer && nextStreamer.online) {nextStreamerFound = true;break;}}if (nextStreamerFound && currentStreamer != nextStreamerFound) {await updateInfoPanel();setIframeSrc(streamers[currentStreamerIndex].name);addLog("streamer-logs-list",`Switched to random online streamer: ${streamers[currentStreamerIndex].name}`);} else {addLog("streamer-logs-list", "No more online streamers.");}} else {addLog("streamer-logs-list", "No need to change streamer");if ($("#streamer-frame").attr("src") ==="https://www.kcchanphotography.com/resources/website/common/images/loading-spin.svg") {setIframeSrc(currentStreamer.name);}}} catch (error) {addLog("streamer-logs-list", `Error checking streamer status: ${error}`);refreshPage();} finally {StateHelper.clearState("updatingStatus");}}async function updateStreamerOnlineStatus() {try {StateHelper.setState("updatingStatus");const streamerNames = streamers.map((s) => s.name);const streamerData = await getStreamerOnlineStatus(streamerNames);streamers.forEach((streamer) => {streamer.online = streamerData[streamer.name];addLog("online-status-logs-list",`${streamer.name} is ${streamer.online ? "online" : "offline"}`);});await updateInfoPanel();} catch (error) {addLog("online-status-logs-list",`Error updating streamer status: ${error}`);refreshPage();} finally {StateHelper.clearState("updatingStatus");}}async function refreshPage() {const now = Date.now();if (now - lastRefreshTimestamp > CONFIG.refreshCooldown) {StateHelper.setState("refreshing");window.location.href = "https://www.twitch.tv/drops/inventory";} else {addLog("inventory-logs-list","Refresh cooldown in effect, skipping refresh.");}}async function promptForCampaignChoice() {await switchToTab("All Campaigns");setTimeout(async () => {const campaigns = await getCampaigns();const campaignSelect = $("#campaign-select");campaignSelect.empty();campaigns.forEach((campaign) => {const option = createComponent("option", "", campaign, {value: campaign,});campaignSelect.append(option);});CONFIG.availableCampaigns = campaigns;saveConfig();$("#campaign-selection-popup").show();}, CONFIG.tabSwitchDelay);}async function main() {try {StateHelper.setState("initializing");if (!CONFIG.selectedCampaign) {await promptForCampaignChoice();} else {StateHelper.setState("fetching");$("#streamer-frame-container").show();await initStreamers();await checkDropsAndUpdateStreamers();await checkStreamerStatus();StateHelper.setState("streaming");setInterval(checkDropsAndUpdateStreamers, CONFIG.checkDropsInterval);setInterval(checkStreamerStatus, CONFIG.checkStreamerStatusInterval);setInterval(updateStreamerOnlineStatus,CONFIG.updateStreamerOnlineStatusInterval);setInterval(refreshPage, CONFIG.pageRefreshInterval);setInterval(watchdogTimer, CONFIG.watchdogInterval);setInterval(updateRunningTime, 1000);}} catch (error) {StateHelper.setState("error");addLog("inventory-logs-list", `Error in main function: ${error}`);refreshPage();} finally {StateHelper.clearState("initializing");StateHelper.clearState("fetching");}}function watchdogTimer() {const currentTime = Date.now();if (currentTime - lastActivityTimestamp > CONFIG.watchdogInterval * 2) {if (initialDataLoaded) {if (consecutiveRefreshCount < maxConsecutiveRefreshes) {addLog("inventory-logs-list","Watchdog timer triggered, refreshing page.");consecutiveRefreshCount++;refreshPage();} else {addLog("inventory-logs-list","Max consecutive refreshes reached. Pausing refreshes.");setTimeout(() => {consecutiveRefreshCount = 0;}, CONFIG.refreshCooldown);}} else {addLog("inventory-logs-list","Watchdog timer check: waiting for initial data to load.");}} else {addLog("inventory-logs-list","Watchdog timer check: script is running fine.");consecutiveRefreshCount = 0;}}function updateLastActivity() {lastActivityTimestamp = Date.now();addLog("inventory-logs-list", "Heartbeat log: script is running.");}function updateRunningTime() {const currentTime = Date.now();const elapsedTime = currentTime - startTime;const hours = Math.floor(elapsedTime / 3600000);const minutes = Math.floor((elapsedTime % 3600000) / 60000);const seconds = Math.floor((elapsedTime % 60000) / 1000);$("#bot-running-time").text(`${hours}h ${minutes}m ${seconds}s`);}const originalSetInterval = setInterval;const originalSetTimeout = setTimeout;window.setInterval = function (callback, interval) {const wrappedCallback = function () {updateLastActivity();callback();};return originalSetInterval(wrappedCallback, interval);};window.setTimeout = function (callback, timeout) {const wrappedCallback = function () {updateLastActivity();callback();};return originalSetTimeout(wrappedCallback, timeout);};window.addEventListener("load", () => {applyStyles();createStreamerFrame();createMainPopup();createCampaignSelectionPopup();actionPill = createActionButton();loadGlobalLogs();addEventListeners();setTimeout(main, 10000);});function setIframeSrc(streamerName) {const currentStreamer = streamers[currentStreamerIndex];const streamerTitle = `${currentStreamer.name} - ${currentStreamer.online ? "Online" : "Offline"} - ${currentStreamer.claimedItems.length}/${currentStreamer.allItems}`;$("#streamer-title").text(streamerTitle);$("#streamer-frame").attr("src",`https://player.twitch.tv/?channel=${streamerName}&parent=www.twitch.tv&muted=${CONFIG.muteIframe === "true"}&quality=${CONFIG.iframeQuality}`);}})();