// ==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
}();