NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Twitch Clips shortcuts (new UI) & Autoclaim // @description Add shortcuts to clips page & claims channel points. // @author Arxk // @namespace https://gist.github.com/Arxk/e891a1d1c90d9004ac5bef34de2971b9 // @homepageURL https://github.com/Arxk/scripts // @license MIT // @version 1.2 // @match https://www.twitch.tv/* // @grant none // ==/UserScript== // ==OpenUserJS== // @author Arxk // ==/OpenUserJS== /** * Declare insertionQ function to detect created DOM elements * https://github.com/naugtur/insertionQuery * * insertion-query v1.0.3 (2016-01-20) * license:MIT * Zbyszek Tenerowicz <naugtur@gmail.com> (http://naugtur.pl/) */ var insertionQ = (function () { "use strict"; var sequence = 100, isAnimationSupported = false, animationstring = 'animationName', keyframeprefix = '', domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), pfx = '', elm = document.createElement('div'), options = { strictlyNew: true, timeout: 20 }; if (elm.style.animationName) { isAnimationSupported = true; } if (isAnimationSupported === false) { for (var i = 0; i < domPrefixes.length; i++) { if (elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { pfx = domPrefixes[i]; animationstring = pfx + 'AnimationName'; keyframeprefix = '-' + pfx.toLowerCase() + '-'; isAnimationSupported = true; break; } } } function listen(selector, callback) { var styleAnimation, animationName = 'insQ_' + (sequence++); var eventHandler = function (event) { if (event.animationName === animationName || event[animationstring] === animationName) { if (!isTagged(event.target)) { callback(event.target); } } }; styleAnimation = document.createElement('style'); styleAnimation.innerHTML = '@' + keyframeprefix + 'keyframes ' + animationName + ' { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }' + "\n" + selector + ' { animation-duration: 0.001s; animation-name: ' + animationName + '; ' + keyframeprefix + 'animation-duration: 0.001s; ' + keyframeprefix + 'animation-name: ' + animationName + '; ' + ' } '; document.head.appendChild(styleAnimation); var bindAnimationLater = setTimeout(function () { document.addEventListener('animationstart', eventHandler, false); document.addEventListener('MSAnimationStart', eventHandler, false); document.addEventListener('webkitAnimationStart', eventHandler, false); //event support is not consistent with DOM prefixes }, options.timeout); //starts listening later to skip elements found on startup. this might need tweaking return { destroy: function () { clearTimeout(bindAnimationLater); if (styleAnimation) { document.head.removeChild(styleAnimation); styleAnimation = null; } document.removeEventListener('animationstart', eventHandler); document.removeEventListener('MSAnimationStart', eventHandler); document.removeEventListener('webkitAnimationStart', eventHandler); } }; } function tag(el) { el.QinsQ = true; //bug in V8 causes memory leaks when weird characters are used as field names. I don't want to risk leaking DOM trees so the key is not '-+-' anymore } function isTagged(el) { return (options.strictlyNew && (el.QinsQ === true)); } function topmostUntaggedParent(el) { if (isTagged(el.parentNode) || el.nodeName === 'BODY') { return el; } else { return topmostUntaggedParent(el.parentNode); } } function tagAll(e) { if (!e) { return; } tag(e); e = e.firstChild; for (; e; e = e.nextSibling) { if (e !== undefined && e.nodeType === 1) { tagAll(e); } } } //aggregates multiple insertion events into a common parent function catchInsertions(selector, callback) { var insertions = []; //throttle summary var sumUp = (function () { var to; return function () { clearTimeout(to); to = setTimeout(function () { insertions.forEach(tagAll); callback(insertions); insertions = []; }, 10); }; })(); return listen(selector, function (el) { if (isTagged(el)) { return; } tag(el); var myparent = topmostUntaggedParent(el); if (insertions.indexOf(myparent) < 0) { insertions.push(myparent); } sumUp(); }); } //insQ function var exports = function (selector) { if (isAnimationSupported && selector.match(/[^{}]/)) { if (options.strictlyNew) { tagAll(document.body); //prevents from catching things on show } return { every: function (callback) { return listen(selector, callback); }, summary: function (callback) { return catchInsertions(selector, callback); } }; } else { return false; } }; //allows overriding defaults exports.config = function (opt) { for (var o in opt) { if (opt.hasOwnProperty(o)) { options[o] = opt[o]; } } }; return exports; })(); if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { module.exports = insertionQ; } /** * * Start of script * */ var elementReady = function (selector) { return new Promise(function (resolve) { var el = document.querySelector(selector); if (el) { resolve(el); } new MutationObserver(function (mutationRecords,observer) { Array.from(document.querySelectorAll(selector)).forEach(function (element) { resolve(element); observer.disconnect(); }); }).observe(document.documentElement, { childList:true, subtree:true }); }); }; var htmlToElement = function (html) { var template = document.createElement('template'); template.innerHTML = html.trim(); return template.content.firstChild; }; /** * Auto reclaim channel points */ insertionQ('div[data-test-selector="community-points-summary"] button.tw-button--success').every(function (claim) { claim.click(); }); /** * Profile button opens to Clips page */ insertionQ('a[data-a-target="watch-mode-to-home"]').every(function (home) { home.onclick = function () { elementReady('a[data-a-target="channel-home-tab-Clips"]').then(function (clips) { clips.click(); }); }; }); /** * Add a link to Clips on the channel header */ insertionQ('.home-header-sticky ul').every(function (menu) { var clips = htmlToElement('<li class="tw-align-items-center tw-c-text-base tw-flex-grow-0 tw-full-height tw-justify-content-center tw-tabs__tab" role="presentation" data-index="5"><a class="tw-block tw-c-text-inherit tw-full-height tw-full-width tw-interactive tw-pd-x-1 tw-tab-item" role="tab" data-a-target="channel-home-tab-Clips" href="' + menu.firstChild.firstChild.href + '/clips?filter=clips&range=24hr"data-ss1593159595="1"><div class="tw-align-left tw-flex tw-flex-column tw-full-height"><div class="tw-flex-grow-0"><div class="tw-font-size-4 tw-semibold">Clips</div></div><div class="tw-flex-grow-1"></div><div class="tw-flex-grow-0"></div></div></a></li>'); clips.firstChild.onclick = function (e) { document.querySelector('a[data-a-target="channel-home-tab-Videos"]').click(); elementReady('button[data-a-target="video-type-filter-dropdown"]').then(function (dropdown) { dropdown.click(); document.querySelector('a[data-a-target="video-type-filter-clips"]').click(); document.querySelector('button[data-a-target="time-filter-selection"]').click(); document.querySelector('a[data-a-target="time-filter-option-24hr"]').click(); }); e.preventDefault(); }; menu.insertBefore(clips, menu.lastChild); // Style Clips link without FFZ if (typeof ffz === "undefined"){ var active = document.querySelector('a[data-a-target="channel-home-tab-Clips"]').firstChild.lastChild; var clipsStatus = function () { active.clearChildren(); active.parentNode.parentNode.classList.remove('tw-c-text-link'); active.parentNode.parentNode.classList.add('tw-c-text-base'); setTimeout(function () { if (location.pathname.indexOf('/clips') > -1) { active.clearChildren(); active.parentNode.parentNode.classList.remove('tw-c-text-base'); active.parentNode.parentNode.classList.add('tw-c-text-link'); active.appendChild(htmlToElement('<div class="tw-tabs__active-indicator" data-test-selector="ACTIVE_TAB_INDICATOR"></div>')); } }, 250); }; document.querySelector('a[data-a-target="channel-home-tab-Home"]').parentNode.onclick = clipsStatus; document.querySelector('a[data-a-target="channel-home-tab-About"]').parentNode.onclick = clipsStatus; document.querySelector('a[data-a-target="channel-home-tab-Schedule"]').parentNode.onclick = clipsStatus; document.querySelector('a[data-a-target="channel-home-tab-Videos"]').parentNode.onclick = clipsStatus; document.querySelector('a[data-a-target="channel-home-tab-Clips"]').parentNode.onclick = clipsStatus; } }); addEventListener('load', function () { // Style Clips link with FFZ if (typeof ffz !== 'undefined') { ffz.site.router.on(':route', function (route) { var clips = document.querySelector('a[data-a-target="channel-home-tab-Clips"]'); if(clips !== null) { var active = clips.firstChild.lastChild; active.clearChildren(); active.parentNode.parentNode.classList.remove('tw-c-text-link'); active.parentNode.parentNode.classList.add('tw-c-text-base'); if (route && route.name === 'user-clips') { active.clearChildren(); active.parentNode.parentNode.classList.remove('tw-c-text-base'); active.parentNode.parentNode.classList.add('tw-c-text-link'); active.appendChild(htmlToElement('<div class="tw-tabs__active-indicator" data-test-selector="ACTIVE_TAB_INDICATOR"></div>')); } } }); } }); if (typeof Element.prototype.clearChildren === 'undefined') { Object.defineProperty(Element.prototype, 'clearChildren', { configurable: true, enumerable: false, value: function () { while (this.firstChild) this.removeChild(this.lastChild); } }); }