Greasy Fork is available in English.

Twitch Automation

Removes annoyances and automates tasks.

Instalirajte ovu skriptu?
Autorov prijedlog skripta

Možda ti se također svidi Youtube Automation.

Instalirajte ovu skriptu
// ==UserScript==
// @name					Twitch Automation
// @namespace				https://greasyfork.org/scripts?set=439787
// @homepage				https://greasyfork.org/scripts/419581-twitch-automation
// @version					0.9.9
// @description				Removes annoyances and automates tasks.
// @author					V. H.
// @match					http*://*.twitch.tv/*
// @require					https://greasyfork.org/scripts/419588-uniq/code/UniQ.js
// @require					https://greasyfork.org/scripts/419717-tmi/code/TMI.js
// @require					https://greasyfork.org/scripts/427124-filtera/code/Filtera.js
// @run-at					document-body
// @icon					https://greasyfork.s3.us-east-2.amazonaws.com/537wudoj7gza51m7tlgls50pen1h
// @grant					unsafeWindow
// @grant					GM_log
// @grant					GM_addStyle
// @grant					GM_setValue
// @grant					GM_getValue
// @grant					GM_listValues
// @grant					GM_deleteValue
// @grant					GM_registerMenuCommand
// @grant					GM_notification
// @grant					GM_webRequest
// @grant					window.onurlchange
// @compatible				Chrome
// @license					AFL-3.0
// @webRequest				[{"selector":"*://sentry.io/*","action":"cancel"},{"selector":"*://*.sentry.io/*","action":"cancel"},{"selector":"*://*.twitchcdn.net/assets/features.video-player.components.video-ads*","action":"cancel"},{"selector":"*://*.scorecardresearch.com/*","action":"cancel"}]
// @noframes				
// @antifeature	tracking	Keeps log of chat and has access to your account's messaging abilities (once permitted), to allow you to see moderator-deleted messages and past notifications or/and automate chatting, THIS DATA IS NOT EXPORTED FROM YOUR DEVICE, PLEASE USE WISELY, CAREFULLY AND NOT MALICIOUSLY.
// ==/UserScript==

/**
 * USAGE NOTES:
 * 
 * 	To take advantage of the autosend functionality,
 * 		you need to grab your account's username from here:		https://www.twitch.tv/settings/profile
 * 		and the OAuth token from here:							https://twitchapps.com/tmi/
 * 	After that, trigger the Panel in tampermonkey/greasemonkey menu (P).
 * 
 * The reason is that this script uses TMI.js library of Twitch to send messages through their Official API since UI-botting has been deemed illegal.
 * 
 * You can block these trackers (with AdBlock filter):
 * 	||app.link$document,subdocument,script,xmlhttprequest,image,other,websocket,object
 * 	||sb.scorecardresearch.com$document,subdocument,script,media,image,object,xmlhttprequest,websocket
 * 	||twitch.tv##div.prime-offers__pill,#amazon-video-ads-iframe,#amznidpxl,iframe[src*='amazon-adsystem']
 * 	||-ads?$domain=static.twitchcdn.net,script,image,media,xmlhttprequest,document
 * 	@@||twitchcdn.net/assets/pages$script,stylesheet,font
 */

void async function _script() {
	"use strict";
	
	const hidecss = `
	visibility: hidden !important;
	display: none !important;
	width: 0 !important;
	height: 0 !important;
	opacity: 0 !important;
	user-select: none !important;
	pointer-events: none !important;
	`,
		banlinks = /(?<=(^|[\&\?\_\.\-\/\b\W]))(ads?|bebi|sentry)(?=([\&\?\_\.\-\/\b\W]|$))/gmi;
	
	var targ = document.getElementsByTagName("video");
	
	if (targ) targ = targ[0];
	
	if (unsafeWindow._FLT) {
		unsafeWindow._FLT.autolay = false;
		GM_addStyle(unsafeWindow._FLT.style);
	}
	
	try {
		Object.defineProperty(document, "hidden", { value: false, writable: false, configurable: false });
		document.hasFocus = function hasFocus() { return true; };
		document.dispatchEvent(new Event("visibilitychange"));
		document.addEventListener("visibilitychange", e => {
			e.stopImmediatePropagation();
			e.stopPropagation();
			e.preventDefault();
			
			return false;
		}, true, true);
		Object.defineProperty(document, "webkitVisibilityState", { value: "visible", writable: false, configurable: false });
		Object.defineProperty(document, "visibilityState", { value: "visible", writable: false, configurable: false });
	} catch(ign) { }
	
	// Optional Styling, feel free to comment-out
	GM_addStyle(`
		/* ~ V. H. */
		textarea {
			caret-color: aquamarine;
		}
		video {
			cursor: cell;
		}
		img, video, svg {
			border-radius: 3px;
		}
		
		#_TA_Panel {
			position: fixed;
			top: 1vh;
			left: 5vw;
			display: block;
			margin: 5px;
			padding: 5px;
			box-sizing: border-box;
			border-radius: 15px;
			background-image: radial-gradient(circle closest-side at center, rgba(200, 200, 200, .9) 10%, rgba(100, 100, 100, 1) 110%);
			background-position: center;
			background-size: cover;
			opacity: .5;
			z-index: 99999;
			box-shadow: 1px 1px 1px 2px rgba(100, 100, 100, .7);
			max-width: 45vw;
			max-height: 40vh;
			width: 25vw;
			min-width: 10vw;
			min-height: 10vh;
			transition-duration: 500ms;
			transition-timing-function: ease-in-out;
			transition-delay: 10ms;
			transition-property: border, opacity, box-shadow, visibility;
			resize: both;
			user-select: none;
			touch-callout: none;
			font-size: 1.2em;
			text-align: center;
			vertical-align: middle;
			overflow: auto;
		}
		#_TA_Panel:hover {
			opacity: 0.7;
			transition-duration: 500ms;
		}
		#_TA_Panel * {
			border-radius: inherit;
			transition-duration: 500ms;
			transition-timing-function: ease-in-out;
			transition-delay: 1ms;
			transition-property: border, opacity, box-shadow, visibility;
			margin: 3px;
			padding: 1px;
			min-width: 1%;
			min-height: 1%;
			overflow: hidden;
		}
		
		#_TA_Panel button, #_TA_Panel input[type="checkbox"] {
			display: inline-block;
			border: 1px outset gray;
			cursor: pointer;
		}
		#_TA_Panel button:disabled {
			cursor: not-allowed;
		}
		#_TA_Panel button:hover {
			box-shadow: 1px 1px 0px 2px rgba(150, 150, 150, .7);
		}
		#_TA_Panel button:active {
			box-shadow: 1px 1px 1px 1px rgba(70, 70, 70, .9);
		}
		#_TA_Panel fieldset {
			display: flex;
			flex: 1 0 auto;
			vertical-align: middle;
			text-align: center;
			flex-flow: column wrap;
			justify-content: center;
			overflow-y: auto;
			align-items: center;
			align-content: center;
		}
		#_TA_Panel input[type="range"] {
			cursor: ew-resize;
		}
		#_TA_Panel input {
			transform: scale(90%);
		}
		#_TA_Panel label {
			font-size: .7em;
			pointer-events: none;
		}
		#_TA_Panel label::after {
			content: ": ";
		}
		
		#_TA_Panel::-webkit-scrollbar, #_TA_Panel::-webkit-thumb, #_TA_Panel::-webkit-track, #_TA_Panel *::-webkit-scrollbar, #_TA_Panel *::-webkit-thumb, #_TA_Panel *::-webkit-track {
			min-width: 3px;
			max-width: 10px;
			background-color: gray;
			color: lightgray;
		}
		#_TA_Panel::-webkit-scrollbar-button, #_TA_Panel::-webkit-scrollbar-corner, #_TA_Panel::-webkit-scrollbar-resizer, #_TA_Panel *::-webkit-scrollbar-button, #_TA_Panel *::-webkit-scrollbar-corner, #_TA_Panel *::-webkit-scrollbar-resizer {
			transform: scale(80%);
			background-color: gray;
			color: lightgray;
		}
		
		#_TA_Logs {
			height: 10vh;
			width: 95%;
			min-height: 5vh;
			min-width: 50%;
			max-height: 20vh;
			max-width: 100%;
			font-size: .5em !important;
			overflow-y: scroll !important;
			scrollbar-width: thin;
			scrollbar-gutter: stable;
			pointer-events: all !important;
		}
		
		div.prime-offers__pill, #amazon-video-ads-iframe, #amznidpxl, iframe[src*='amazon-adsystem'] {
			${hidecss}
		}
		.embed-upsell-post-preview--container {
			${hidecss}
		}
		._TA_hide {
			transition-property: width, height, border, opacity, box-shadow, visibility;
			${hidecss}
		}
		/*
		.celebration__overlay {
			${hidecss}
		}
		*/
	`);
	
	unsafeWindow.interXHR();
	unsafeWindow.interFetch();
	unsafeWindow.interXHR.add(function open(method, url, ...rest) {
		if (banlinks.test(url)) {
			GM_log("Blocked:", method, url, ...rest);
			
			return true;
		} else return false;
	});
	unsafeWindow.interFetch.add(function fetch(url, init, ...rest) {
		if (banlinks.test(url)) {
			GM_log("Blocked:", url, init, ...rest);
			
			return true;
		} else if (/(?<=\/)(hls|vod)(?=\/)/i.test(url) && !url.includes("picture-by-picture")) {
			const m = url.match(/(?<=\/)(hls|vod)(?=\/)(.*)$/i);
			
			if (m) {
				GM_log("Blocked Ad:", url, init, ...rest);
				
				return true;
			}
			
			return unsafeWindow.interFetch.interOrig.call(this, url, init, ...rest);
		}/* else if (url.includes("/access_token")) { //fetch
			url = url.replace("player_type=embed", "player_type=site");
			
			GM_log("Embeded:", url, init, ...rest);
			
			return unsafeWindow.interFetch.interOrig.call(this, url, init, ...rest);
		}*/ else if (url.includes("/gql") && init &&
				typeof init.body === "string" &&
				init.body.includes("PlaybackAccessToken")) { //fetch
			const newBody = JSON.parse(init.body);
			newBody.variables.playerType = "site";
			init.body = JSON.stringify(newBody);
			
			GM_log("BlockedGQL:", url, init, ...rest);
			
			return unsafeWindow.interFetch.interOrig.call(this, url, init, ...rest);
		} else return false;
	});
	
	// Focus chat
	const textfix = () => unsafeWindow.do_if(document.querySelector("textarea[aria-label^='Send a']"), tex => {
		tex.setAttributeNode(document.createAttribute("autofocus"));
		tex.focus();
		
		return true;
	}),
	// Calculate ID string
	calc_str = () => {
		return `${document.title.replace(/ /gmis, '').replace(/-/gmis, '_').toLowerCase()}-`;
	},
	// Autoclaim
	observefix = (w, m) => unsafeWindow.do_if(document.querySelector(w), (d, mut) => {
		mut.disconnect();
		mut.observe(d, { childList: true, subtree: true/*, characterData: true, characterDataOldValue: true*/ });
		
		return true;
	}, m),
	// Stop Autoplay
	stopp = () => unsafeWindow.do_if(document.querySelector("[aria-label^=Pause]"), play => {
		var vid = document.getElementsByTagName("video");
		
		if (location.pathname.length <= 1 && document.readyState === "complete") {
			const m = document.querySelector("button[aria-label^='Mute']");
			
			unsafeWindow.sleep(700).then(() => play.click());
			
			if (m) m.click();
			if (vid.length && (vid = vid[0])) {
				vid.preload = "none";
				vid.setAttribute("autoplay", false);
				vid.setAttributeNode(document.createAttribute("muted"));
				vid.pause();
				vid.addEventListener("play", () => vid.pause(), { once: true });
			}
			
			GM_log("Playback stopped.");
			unsafeWindow.incVal("stops");
			
			return true;
		} else return false;
	}),
	// Panel
	once_setup = () => {
		unsafeWindow.connect = connect;
		unsafeWindow.panel = panel;
		
		if (!document.body || document.readyState !== "complete") return false;
		else if (location.hostname === "www.twitch.tv") GM_registerMenuCommand("Panel", panel, "P");
		
		const popup = document.createElement("dialog"), //dialog
			clearb = document.createElement("button"), //clean logs
			closeb = document.createElement("button"), //close popup
			autot = document.createElement("textarea"), //send text
			autos = document.createElement("input"), //send enable
			autol = document.createElement("label"), //send enable label
			fields = document.createElement("fieldset"), //group
			fieldl = document.createElement("legend"), //groupname
			rand = document.createElement("input"), //randomness
			delay = document.createElement("input"), //delay
			oauth = document.createElement("input"), //OAuth of bot account
			usrnam = document.createElement("input"), //Username of bot account
			conn = document.createElement("button"), //Connect
			form = document.createElement("form"), //Form
			logs = document.createElement("textarea"), //Logs
			flt = unsafeWindow.flt = new unsafeWindow._FLT(targ, (prop, val) => {
				if (!targ) {
					targ = document.getElementsByTagName("video");
					
					if (targ) targ = targ[0];
				} else if (targ || !flt.wrp) flt.wrp = targ;
				
				return false;
			});
		
		flt.root = form;
		flt.lay();
		
		popup.classList.toggle("_TA_hide");
		popup.id = "_TA_Panel";
		fieldl.innerText = "AutoSender";
		autot.placeholder = "Text to Send at intervals..";
		autot.tile = "Text to automatically Send.";
		autot.defaultValue = GM_getValue(unsafeWindow.str + "msg", '');
		autos.type = "checkbox";
		autos.id = "_TA_Check";
		autos.title = "Enable Sending.";
		autol.for = "_TA_Check";
		autol.innerText = "Enabled";
		delay.type = "number";
		delay.pattern = "[0-9]+";
		delay.minlength = 1;
		delay.maxlength = 15;
		delay.defaultValue = 1 * GM_getValue(unsafeWindow.str + "delay", 5 * 60 * 1000); //5 mins
		delay.placeholder = "Delay";
		delay.title = "Send delay.";
		rand.type = "number";
		rand.pattern = "[0-9]+";
		rand.minlength = 1;
		rand.maxlength = 15;
		rand.defaultValue = 1 * GM_getValue(unsafeWindow.str + "random", 60 * 1000); //1 min
		rand.placeholder = "Randomness";
		rand.title = "Input a number which will be multiplied by a random number between [0, 1] and added to the original Send delay.";
		oauth.type = "password";
		oauth.placeholder = "OAuth";
		oauth.title = "OAuth token of bot account."; // https://twitchapps.com/tmi/
		oauth.autocomplete = "current-password";
		oauth.defaultValue = GM_getValue("oauth", ((unsafeWindow._cookies || []).find(c => c[0] == "auth-token") || ['', ''])[1]);
		usrnam.type = "text";
		usrnam.placeholder = "Username";
		usrnam.title = "Username of bot account.";
		usrnam.autocomplete = "username";
		usrnam.defaultValue = GM_getValue("username", ((unsafeWindow._cookies || []).find(c => c[0] == "name") || ['', ''])[1]);
		clearb.innerText = "Clean Chat Logs";
		clearb.title = "Clean Chat Logs of Current Stream";
		clearb.onclick = () => {
			for (const val of GM_listValues())
				if (val !== "username" && val !== "quality") GM_deleteValue(val);
			
			GM_log("Logs cleaned.");
		};
		closeb.innerText = "Hide Panel";
		closeb.title = "Toggle this Panel";
		closeb.onclick = () => panel();
		conn.innerText = "Connect";
		conn.title = "Connect bot.";
		conn.onclick = () => connect();
		form.onsubmit = e => { e.preventDefault(); return false; };
		logs.autocomplete = "off";
		logs.name = "logs";
		logs.placeholder = "Logs...";
		logs.setAttribute("readonly", "");
		logs.spellcheck = false;
		logs.id = "_TA_Logs";
		
		popup.appendChild(clearb);
		popup.appendChild(closeb);
		popup.appendChild(document.createElement("br"));
		fields.appendChild(fieldl);
		fields.appendChild(autot);
		fields.appendChild(delay);
		fields.appendChild(rand);
		fields.appendChild(autol);
		fields.appendChild(autos);
		fields.appendChild(usrnam);
		fields.appendChild(oauth);
		fields.appendChild(conn);
		form.appendChild(fields);
		flt.addTo(popup);
		popup.appendChild(logs);
		document.body.appendChild(popup);
		
		function log(...data) {
			logs.value += data.join(' ') + `  {${(new Date()).toString()}}` + '\n';
			
			logs.scrollTop = Number(logs.scrollTop) + 70;
			logs.scrollBy(5, 70);
			
			return logs.value;
		} //log
		if (!unsafeWindow.logl) unsafeWindow.logl = log;
		
		function panel() {
			popup.toggleAttribute("open");
			popup.classList.toggle("_TA_hide");
			
			return true;
		} //panel
		
		async function connect(user = usrnam.value || GM_getValue("username", null), pass = oauth.value || GM_getValue("oauth", null), channel = '#' + location.pathname.substring(1)) {
			if (unsafeWindow.sndIntrvl) clearTimeout(unsafeWindow.sndIntrvl);
			if (unsafeWindow.client && [ "CONNECTING", "OPEN" ].includes(unsafeWindow.client.readyState())) await unsafeWindow.client.disconnect();
			
			conn.toggleAttribute("disabled");
			
			user = user.toLowerCase();
			
			GM_setValue("username", user);
			GM_setValue("oauth", pass);
			
			unsafeWindow.client = new window.tmi.Client({
				identity: {
					username: user,
					password: pass,
				},
				connection: {
					reconnect: true,
					secure: true,
				},
				options: {
					debug: true,
					globalDefaultChannel: '#' + user,
					clientId: GM_getValue("id", null),
					joinInterval: 300,
					skipUpdatingEmotesets: true,
					updateEmotesetsTimer: 600000,
				},
				channels: [ channel ]
			});
			
			const servport = await unsafeWindow.client.connect();
			
			GM_log(`TMI connected to ${servport[0]}:${servport[1]}, listening: ${channel}`);
			
			unsafeWindow.logs			= unsafeWindow.logs			|| new Map();
			unsafeWindow.bans			= unsafeWindow.bans			|| new Map();
			unsafeWindow.joins			= unsafeWindow.joins		|| new Map();
			unsafeWindow.parts			= unsafeWindow.parts		|| new Map();
			unsafeWindow.raids			= unsafeWindow.raids		|| new Map();
			unsafeWindow.times			= unsafeWindow.times		|| new Map();
			unsafeWindow.remlogs		= unsafeWindow.remlogs		|| new Map();
			unsafeWindow.subgiftlogs	= unsafeWindow.subgiftlogs	|| new Map();
			unsafeWindow.resublogs		= unsafeWindow.resublogs	|| new Map();
			unsafeWindow.submystlogs	= unsafeWindow.submystlogs	|| new Map();
			unsafeWindow.sublogs		= unsafeWindow.sublogs		|| new Map();
			unsafeWindow.unmodlogs		= unsafeWindow.unmodlogs	|| new Map();
			
			unsafeWindow.client.on("chat", (channel, userstate, message, self) => {
				if (!unsafeWindow.logs.has(channel)) unsafeWindow.logs.set(channel, [ ]);
				
				const arr = unsafeWindow.logs.get(channel);
				
				arr.push([message, userstate, self, Date.now()]);
				unsafeWindow.logs.set(channel, arr);
			});
			unsafeWindow.client.on("ban", (channel, username, reason, userstate) => {
				if (!unsafeWindow.bans.has(channel)) unsafeWindow.bans.set(channel, [ ]);
				
				const arr = unsafeWindow.bans.get(channel);
				
				arr.push([username, reason, userstate, Date.now()]);
				log(`Ban: `, `< ${username} >`, ` (${reason})\n`);
				unsafeWindow.bans.set(channel, arr);
			});
			unsafeWindow.client.on("join", (channel, username, self) => {
				if (!unsafeWindow.joins.has(channel)) unsafeWindow.joins.set(channel, [ ]);
				
				const arr = unsafeWindow.joins.get(channel);
				
				arr.push([username, Date.now()]);
				log(`Join: `, `< ${username} >`);
				unsafeWindow.joins.set(channel, arr);
			});
			unsafeWindow.client.on("part", (channel, username, self) => {
				if (!unsafeWindow.parts.has(channel)) unsafeWindow.parts.set(channel, [ ]);
				
				const arr = unsafeWindow.parts.get(channel);
				
				arr.push([username, Date.now()]);
				log(`Left: `, `< ${username} >`);
				unsafeWindow.parts.set(channel, arr);
			});
			unsafeWindow.client.on("raided", (channel, username, viewers) => {
				if (!unsafeWindow.raids.has(channel)) unsafeWindow.raids.set(channel, [ ]);
				
				const arr = unsafeWindow.raids.get(channel);
				
				arr.push([username, viewers, Date.now()]);
				log(`Raid: `, `< ${username} >`, ` (${viewers})`);
				unsafeWindow.raids.set(channel, arr);
			});
			unsafeWindow.client.on("timeout", (channel, username, reason, duration, userstate) => {
				if (!unsafeWindow.times.has(channel)) unsafeWindow.times.set(channel, [ ]);
				
				const arr = unsafeWindow.times.get(channel);
				
				arr.push([username, reason, duration, userstate, Date.now()]);
				log(`Mute: `, `< ${username} >`, ` [for: ${duration.toString().slice(0, 3)}]`, ` (${reason})\n`);
				unsafeWindow.times.set(channel, arr);
			});
			unsafeWindow.client.on("messagedeleted", (channel, username, deletedMessage, userstate) => {
				if (!unsafeWindow.remlogs.has(channel)) unsafeWindow.remlogs.set(channel, [ ]);
				
				const arr = unsafeWindow.remlogs.get(channel);
				
				arr.push([deletedMessage, username, userstate, Date.now()]);
				log(`Delete: `, `< ${username} >`, ` (${deletedMessage})\n`);
				unsafeWindow.remlogs.set(channel, arr);
			});
			unsafeWindow.client.on("resub", (channel, username, months, message, userstate, methods) => {
				if (!unsafeWindow.resublogs.has(channel)) unsafeWindow.resublogs.set(channel, [ ]);
				
				const arr = unsafeWindow.resublogs.get(channel);
				
				arr.push([username, months, message, userstate, methods, Date.now()]);
				log(`Resub: `, `< ${username} >`, ` (${months} months: ${message})\n`);
				unsafeWindow.resublogs.set(channel, arr);
			});
			unsafeWindow.client.on("subgift", (channel, username, streakMonths, recipient, methods, userstate) => {
				if (!unsafeWindow.subgiftlogs.has(channel)) unsafeWindow.subgiftlogs.set(channel, [ ]);
				
				const arr = unsafeWindow.subgiftlogs.get(channel);
				
				arr.push([username, streakMonths, recipient, userstate, methods, Date.now()]);
				log(`Subgift: `, `< ${username} >`, ` (${streakMonths} months: ${recipient})`);
				unsafeWindow.subgiftlogs.set(channel, arr);
			});
			unsafeWindow.client.on("submysterygift", (channel, username, numbOfSubs, methods, userstate) => {
				if (!unsafeWindow.submystlogs.has(channel)) unsafeWindow.submystlogs.set(channel, [ ]);
				
				const arr = unsafeWindow.submystlogs.get(channel);
				
				arr.push([username, numbOfSubs, userstate, methods, Date.now()]);
				log(`MysterySub: `, `< ${username} >`, ` (${numbOfSubs} subs)`);
				unsafeWindow.submystlogs.set(channel, arr);
			});
			unsafeWindow.client.on("subscription", (channel, username, methods, message, userstate) => {
				if (!unsafeWindow.sublogs.has(channel)) unsafeWindow.sublogs.set(channel, [ ]);
				
				const arr = unsafeWindow.sublogs.get(channel);
				
				arr.push([username, message, userstate, methods, Date.now()]);
				log(`Sub: `, `< ${username} >`, ` (${message})\n`);
				unsafeWindow.sublogs.set(channel, arr);
			});
			unsafeWindow.client.on("unmod", (channel, username) => {
				if (!unsafeWindow.unmodlogs.has(channel)) unsafeWindow.unmodlogs.set(channel, [ ]);
				
				const arr = unsafeWindow.unmodlogs.get(channel);
				
				arr.push([username, Date.now()]);
				log(`Unmod: `, `< ${username} >`);
				unsafeWindow.unmodlogs.set(channel, arr);
			});
			unsafeWindow.client.on("clearchat", channel => log("Clear"));
			unsafeWindow.client.on("pong", lat => GM_log(`TMI server pong ${lat}`));
			unsafeWindow.client.on("ping", () => GM_log("TMI server ping"));
			unsafeWindow.client.on("reconnect", () => GM_log("Reconnecting..."));
			unsafeWindow.client.on("disconnected", (reason) => GM_log(`TMI disconnect ${reason}`));
			
			conn.toggleAttribute("disabled");
			
			async function _send(where = channel, what = autot.value, del = (delay.value || (10 * 60 * 1000)) * 1) {
				GM_setValue(unsafeWindow.str + "msg", autot.value);
				GM_setValue(unsafeWindow.str + "delay", delay.value);
				GM_setValue(unsafeWindow.str + "random", rand.value);
				
				if (autos.checked && what && where) {
					await unsafeWindow.client.say(where, what);
					
					GM_log(`Message sent: '${what}'`);
					GM_setValue(unsafeWindow.str + "sent", GM_getValue(unsafeWindow.str + "sent", 0) + 1);
				}
				
				const next = del + ~~(Math.random() * rand.value);
				
				GM_log(`Next send attempt in ${next}ms`);
				
				return (unsafeWindow.sndIntrvl = setTimeout(_send, next));
			} //_send
			
			await unsafeWindow.try_until(() => unsafeWindow.client.readyState() == "OPEN");
			
			return _send();
		} //connect
		
		if (usrnam.value && oauth.value) connect();
		
		return true;
	},
	claimut = new (unsafeWindow.MutationObserver || unsafeWindow.WebKitMutationObserver || unsafeWindow.MozMutationObserver)(observeclaim);
	
	var delay1 = 0, delay2 = 0, delay3 = 0, et;
	
	// Globals
	unsafeWindow.tmi = unsafeWindow.tmi || window.tmi;
	unsafeWindow.str = calc_str();
	unsafeWindow.addEventListener("urlchange", et = async e => {
		if (!(e && e.url)) e = { url: location.href };
		
		GM_log(`Redirect: ${e.url}`);
		
		const url = new URL(e.url),
			red = GM_getValue(unsafeWindow.str + "redirects", [ ]);
		
		if (url.pathname.length > 1 && !red.includes(e.url)) red.push(e.url);
		if (location.pathname.length > 1) GM_setValue(unsafeWindow.str + "redirects", red);
		
		unsafeWindow.try_once.remove(stopp);
		await unsafeWindow.sleep(1000).then(() => unsafeWindow.try_max(stopp, 5, 700));
		await unsafeWindow.try_max(observefix, 4, 500, "body", claimut);
		
		unsafeWindow.str = calc_str();
		
		targ = document.getElementsByTagName("video");
		
		if (targ) targ = targ[0];
		if (unsafeWindow.client && [ "CONNECTING", "OPEN" ].includes(unsafeWindow.client.readyState())) unsafeWindow.client.disconnect.then(() => unsafeWindow.connect());
	});
	unsafeWindow.addEventListener("locationchange", et);
	
	localStorage.setItem("video-quality", GM_getValue("quality" ,`{"default": "720p45"}`));
	localStorage.setItem("quality-bitrate", 3000000);
	localStorage.setItem("s-qs-ts", Math.floor(Date.now()));
	//unsafeWindow.localStorage.setItem("video-quality", JSON.stringify({ default: "chunked" }));
	
	GM_log("TA loaded.");
	
	await unsafeWindow.try_until(once_setup, 600); //failsafe
	await unsafeWindow.try_max(textfix, 2, 500); //optional
	await unsafeWindow.try_max(observefix, 4, 500, "body", claimut);
	
	unsafeWindow.sleep(1000).then(() => {
		unsafeWindow.try_once(claim);
		unsafeWindow.try_once(err);
		unsafeWindow.try_max(stopp, 5, 700);
	});
	
	// Ticker
	setInterval(() => {
		unsafeWindow._cookies = unsafeWindow.parseCookies();
		
		unsafeWindow.do_if(document.getElementById("amazon-video-ads-iframe"), frame => {
			frame.parentNode.remove();
			GM_log("Ad removed.");
			unsafeWindow.incVal("ads");
			
			return true;
		});
		unsafeWindow.do_if(document.getElementById("amznidpxl"), frame => {
			frame.remove();
			GM_log("Ad removed.");
			unsafeWindow.incVal("ads");
			
			return true;
		});
		unsafeWindow.do_if(document.querySelectorAll("iframe[src*='amazon-adsystem'], script[src*='scorecardresearch'], script[src*='amazon-adsystem'], script[src*='securepubads'], link[href*='video-ads']"), frame => {
			for (const fr of frame) {
				fr.remove();
				GM_log("Ad removed.");
				unsafeWindow.incVal("ads");
			}
			
			return true;
		});
		
		localStorage.setItem("video_ads.stream_loudness", JSON.stringify({ "loudness": 0, "timestamp": Date.now() }));
		
		claim();
		notif();
		err();
		
		GM_setValue("quality", localStorage.getItem("video-quality"));
		unsafeWindow.str = calc_str();
	}, 800);
	
	function observeclaim(mutationlist, selfmutation) {
		// Check Claim/Error
		//for (const mutation of mutationlist) {
			//for (let added of mutation.addedNodes) {
				if (claim()) return (notif(), err(), true);
				else if (err()) return (notif(), true);
				else return (notif(), false);
			//}
		//}
		
		return false;
	} //observeclaim
	function claim() {
		// Click Claim
		if (unsafeWindow.do_if(document.querySelector(".claimable-bonus__icon"), added => {
			if (delay1) return false; // Denies multiclicks
			else setTimeout(() => { delay1 = 0; }, delay1 = 500);
			
			added.click();
			unsafeWindow.incVal(unsafeWindow.str + "claims");
			GM_log("Claim clicked.");
			
			return true;
		})) return true;
		
		return false;
	} //claim
	function notif() {
		if (unsafeWindow.do_if(document.querySelector(".community-highlight"), comm => {
			if (delay2 || !comm.innerText.includes("Predict")) return false; // Denies multiples
			else setTimeout(() => { delay2 = 0; }, delay2 = 10000);
			
			GM_getValue("enable_notifs", true) && GM_notification({ //set to false to disable notifs
				text: `Prediction - ${document.title}`,
				title: "Community Highlight",
				image: (document.querySelector("link[rel='icon']") || { href: "https://static.twitchcdn.net/assets/favicon-32-e29e246c157142c94346.png" }).href,
				highlight: false,
				silent: true,
				timeout: 2000
			}/*, () => (delay2 = 0)*/);
			
			GM_log("Notif.");
			
			return true;
		})) return true;
		
		return false;
	} //notif
	function err() {
		// Refresh
		// @todo failed to load module
		if (unsafeWindow.do_if(document.querySelector("p[data-test-selector='content-overlay-gate__text']"), added => {
			if (delay3) return false; // Denies multiclicks
			else setTimeout(() => { delay3 = 0; }, delay3 = 500);
			
			if (added.innerText && added.innerText.includes("(Error #") && !added.innerText.includes("premium")) {
				unsafeWindow.incVal(unsafeWindow.str + "errors");
				GM_log("Error occured, reloading...");
				
				const re = Array.from(document.querySelectorAll("div[data-a-target='tw-core-button-label-text']")).find(r => /Reload|Player/i.test(r.innerText));
				
				if (re) re.parentNode.parentNode.click();
				else location.reload();
			}
			
			return true;
		}) || unsafeWindow.do_if(document.querySelector("div[data-test-selector='sad-overlay']"), sad => {
			GM_log("Sad detected. Reloading...");
			unsafeWindow.incVal("ads");
			location.reload();
			sad.remove();
			
			return true;
		})) return true;
		
		return false;
	} //err
}();