diff --git a/goodtube.js b/goodtube.js
index 0bbd150..9372c92 100644
--- a/goodtube.js
+++ b/goodtube.js
@@ -6,7 +6,7 @@
------------------------------------------------------------------------------------------ */
// Include DOM Purify (https://github.com/cure53/DOMPurify/blob/main/dist/purify.min.js)
// We're placing this inline this because fetching it from an external repo which don't have control over is likely unreliable in the long term
- !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).DOMPurify = t() }(this, (function () { "use strict"; const { entries: e, setPrototypeOf: t, isFrozen: n, getPrototypeOf: o, getOwnPropertyDescriptor: r } = Object; let { freeze: i, seal: a, create: l } = Object, { apply: c, construct: s } = "undefined" != typeof Reflect && Reflect; i || (i = function (e) { return e }), a || (a = function (e) { return e }), c || (c = function (e, t) { for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), r = 2; r < n; r++)o[r - 2] = arguments[r]; return e.apply(t, o) }), s || (s = function (e) { for (var t = arguments.length, n = new Array(t > 1 ? t - 1 : 0), o = 1; o < t; o++)n[o - 1] = arguments[o]; return new e(...n) }); const u = w(Array.prototype.forEach), m = w(Array.prototype.lastIndexOf), p = w(Array.prototype.pop), f = w(Array.prototype.push), d = w(Array.prototype.splice), h = w(String.prototype.toLowerCase), g = w(String.prototype.toString), T = w(String.prototype.match), y = w(String.prototype.replace), E = w(String.prototype.indexOf), A = w(String.prototype.trim), _ = w(Object.prototype.hasOwnProperty), b = w(RegExp.prototype.test), S = (N = TypeError, function () { for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++)t[n] = arguments[n]; return s(N, t) }); var N; function w(e) { return function (t) { t instanceof RegExp && (t.lastIndex = 0); for (var n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), r = 1; r < n; r++)o[r - 1] = arguments[r]; return c(e, t, o) } } function R(e, o) { let r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : h; t && t(e, null); let i = o.length; for (; i--;) { let t = o[i]; if ("string" == typeof t) { const e = r(t); e !== t && (n(o) || (o[i] = e), t = e) } e[t] = !0 } return e } function D(e) { for (let t = 0; t < e.length; t++) { _(e, t) || (e[t] = null) } return e } function C(t) { const n = l(null); for (const [o, r] of e(t)) { _(t, o) && (Array.isArray(r) ? n[o] = D(r) : r && "object" == typeof r && r.constructor === Object ? n[o] = C(r) : n[o] = r) } return n } function v(e, t) { for (; null !== e;) { const n = r(e, t); if (n) { if (n.get) return w(n.get); if ("function" == typeof n.value) return w(n.value) } e = o(e) } return function () { return null } } const O = i(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), x = i(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), L = i(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), k = i(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), I = i(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), M = i(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), U = i(["#text"]), z = i(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]), P = i(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), F = i(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), H = i(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), B = a(/\{\{[\w\W]*|[\w\W]*\}\}/gm), G = a(/<%[\w\W]*|[\w\W]*%>/gm), W = a(/\$\{[\w\W]*/gm), Y = a(/^data-[\-\w.\u00B7-\uFFFF]+$/), j = a(/^aria-[\-\w]+$/), X = a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i), q = a(/^(?:\w+script|data):/i), $ = a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g), K = a(/^html$/i), V = a(/^[a-z][.\w]*(-[.\w]+)+$/i); var Z = Object.freeze({ __proto__: null, ARIA_ATTR: j, ATTR_WHITESPACE: $, CUSTOM_ELEMENT: V, DATA_ATTR: Y, DOCTYPE_NAME: K, ERB_EXPR: G, IS_ALLOWED_URI: X, IS_SCRIPT_OR_DATA: q, MUSTACHE_EXPR: B, TMPLIT_EXPR: W }); const J = 1, Q = 3, ee = 7, te = 8, ne = 9, oe = function () { return "undefined" == typeof window ? null : window }; var re = function t() { let n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : oe(); const o = e => t(e); if (o.version = "3.3.0", o.removed = [], !n || !n.document || n.document.nodeType !== ne || !n.Element) return o.isSupported = !1, o; let { document: r } = n; const a = r, c = a.currentScript, { DocumentFragment: s, HTMLTemplateElement: N, Node: w, Element: D, NodeFilter: B, NamedNodeMap: G = n.NamedNodeMap || n.MozNamedAttrMap, HTMLFormElement: W, DOMParser: Y, trustedTypes: j } = n, q = D.prototype, $ = v(q, "cloneNode"), V = v(q, "remove"), re = v(q, "nextSibling"), ie = v(q, "childNodes"), ae = v(q, "parentNode"); if ("function" == typeof N) { const e = r.createElement("template"); e.content && e.content.ownerDocument && (r = e.content.ownerDocument) } let le, ce = ""; const { implementation: se, createNodeIterator: ue, createDocumentFragment: me, getElementsByTagName: pe } = r, { importNode: fe } = a; let de = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] }; o.isSupported = "function" == typeof e && "function" == typeof ae && se && void 0 !== se.createHTMLDocument; const { MUSTACHE_EXPR: he, ERB_EXPR: ge, TMPLIT_EXPR: Te, DATA_ATTR: ye, ARIA_ATTR: Ee, IS_SCRIPT_OR_DATA: Ae, ATTR_WHITESPACE: _e, CUSTOM_ELEMENT: be } = Z; let { IS_ALLOWED_URI: Se } = Z, Ne = null; const we = R({}, [...O, ...x, ...L, ...I, ...U]); let Re = null; const De = R({}, [...z, ...P, ...F, ...H]); let Ce = Object.seal(l(null, { tagNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, allowCustomizedBuiltInElements: { writable: !0, configurable: !1, enumerable: !0, value: !1 } })), ve = null, Oe = null; const xe = Object.seal(l(null, { tagCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeCheck: { writable: !0, configurable: !1, enumerable: !0, value: null } })); let Le = !0, ke = !0, Ie = !1, Me = !0, Ue = !1, ze = !0, Pe = !1, Fe = !1, He = !1, Be = !1, Ge = !1, We = !1, Ye = !0, je = !1, Xe = !0, qe = !1, $e = {}, Ke = null; const Ve = R({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]); let Ze = null; const Je = R({}, ["audio", "video", "img", "source", "image", "track"]); let Qe = null; const et = R({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), tt = "http://www.w3.org/1998/Math/MathML", nt = "http://www.w3.org/2000/svg", ot = "http://www.w3.org/1999/xhtml"; let rt = ot, it = !1, at = null; const lt = R({}, [tt, nt, ot], g); let ct = R({}, ["mi", "mo", "mn", "ms", "mtext"]), st = R({}, ["annotation-xml"]); const ut = R({}, ["title", "style", "font", "a", "script"]); let mt = null; const pt = ["application/xhtml+xml", "text/html"]; let ft = null, dt = null; const ht = r.createElement("form"), gt = function (e) { return e instanceof RegExp || e instanceof Function }, Tt = function () { let e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; if (!dt || dt !== e) { if (e && "object" == typeof e || (e = {}), e = C(e), mt = -1 === pt.indexOf(e.PARSER_MEDIA_TYPE) ? "text/html" : e.PARSER_MEDIA_TYPE, ft = "application/xhtml+xml" === mt ? g : h, Ne = _(e, "ALLOWED_TAGS") ? R({}, e.ALLOWED_TAGS, ft) : we, Re = _(e, "ALLOWED_ATTR") ? R({}, e.ALLOWED_ATTR, ft) : De, at = _(e, "ALLOWED_NAMESPACES") ? R({}, e.ALLOWED_NAMESPACES, g) : lt, Qe = _(e, "ADD_URI_SAFE_ATTR") ? R(C(et), e.ADD_URI_SAFE_ATTR, ft) : et, Ze = _(e, "ADD_DATA_URI_TAGS") ? R(C(Je), e.ADD_DATA_URI_TAGS, ft) : Je, Ke = _(e, "FORBID_CONTENTS") ? R({}, e.FORBID_CONTENTS, ft) : Ve, ve = _(e, "FORBID_TAGS") ? R({}, e.FORBID_TAGS, ft) : C({}), Oe = _(e, "FORBID_ATTR") ? R({}, e.FORBID_ATTR, ft) : C({}), $e = !!_(e, "USE_PROFILES") && e.USE_PROFILES, Le = !1 !== e.ALLOW_ARIA_ATTR, ke = !1 !== e.ALLOW_DATA_ATTR, Ie = e.ALLOW_UNKNOWN_PROTOCOLS || !1, Me = !1 !== e.ALLOW_SELF_CLOSE_IN_ATTR, Ue = e.SAFE_FOR_TEMPLATES || !1, ze = !1 !== e.SAFE_FOR_XML, Pe = e.WHOLE_DOCUMENT || !1, Be = e.RETURN_DOM || !1, Ge = e.RETURN_DOM_FRAGMENT || !1, We = e.RETURN_TRUSTED_TYPE || !1, He = e.FORCE_BODY || !1, Ye = !1 !== e.SANITIZE_DOM, je = e.SANITIZE_NAMED_PROPS || !1, Xe = !1 !== e.KEEP_CONTENT, qe = e.IN_PLACE || !1, Se = e.ALLOWED_URI_REGEXP || X, rt = e.NAMESPACE || ot, ct = e.MATHML_TEXT_INTEGRATION_POINTS || ct, st = e.HTML_INTEGRATION_POINTS || st, Ce = e.CUSTOM_ELEMENT_HANDLING || {}, e.CUSTOM_ELEMENT_HANDLING && gt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck) && (Ce.tagNameCheck = e.CUSTOM_ELEMENT_HANDLING.tagNameCheck), e.CUSTOM_ELEMENT_HANDLING && gt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) && (Ce.attributeNameCheck = e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck), e.CUSTOM_ELEMENT_HANDLING && "boolean" == typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (Ce.allowCustomizedBuiltInElements = e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements), Ue && (ke = !1), Ge && (Be = !0), $e && (Ne = R({}, U), Re = [], !0 === $e.html && (R(Ne, O), R(Re, z)), !0 === $e.svg && (R(Ne, x), R(Re, P), R(Re, H)), !0 === $e.svgFilters && (R(Ne, L), R(Re, P), R(Re, H)), !0 === $e.mathMl && (R(Ne, I), R(Re, F), R(Re, H))), e.ADD_TAGS && ("function" == typeof e.ADD_TAGS ? xe.tagCheck = e.ADD_TAGS : (Ne === we && (Ne = C(Ne)), R(Ne, e.ADD_TAGS, ft))), e.ADD_ATTR && ("function" == typeof e.ADD_ATTR ? xe.attributeCheck = e.ADD_ATTR : (Re === De && (Re = C(Re)), R(Re, e.ADD_ATTR, ft))), e.ADD_URI_SAFE_ATTR && R(Qe, e.ADD_URI_SAFE_ATTR, ft), e.FORBID_CONTENTS && (Ke === Ve && (Ke = C(Ke)), R(Ke, e.FORBID_CONTENTS, ft)), Xe && (Ne["#text"] = !0), Pe && R(Ne, ["html", "head", "body"]), Ne.table && (R(Ne, ["tbody"]), delete ve.tbody), e.TRUSTED_TYPES_POLICY) { if ("function" != typeof e.TRUSTED_TYPES_POLICY.createHTML) throw S('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); if ("function" != typeof e.TRUSTED_TYPES_POLICY.createScriptURL) throw S('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); le = e.TRUSTED_TYPES_POLICY, ce = le.createHTML("") } else void 0 === le && (le = function (e, t) { if ("object" != typeof e || "function" != typeof e.createPolicy) return null; let n = null; const o = "data-tt-policy-suffix"; t && t.hasAttribute(o) && (n = t.getAttribute(o)); const r = "dompurify" + (n ? "#" + n : ""); try { return e.createPolicy(r, { createHTML: e => e, createScriptURL: e => e }) } catch (e) { return console.warn("TrustedTypes policy " + r + " could not be created."), null } }(j, c)), null !== le && "string" == typeof ce && (ce = le.createHTML("")); i && i(e), dt = e } }, yt = R({}, [...x, ...L, ...k]), Et = R({}, [...I, ...M]), At = function (e) { f(o.removed, { element: e }); try { ae(e).removeChild(e) } catch (t) { V(e) } }, _t = function (e, t) { try { f(o.removed, { attribute: t.getAttributeNode(e), from: t }) } catch (e) { f(o.removed, { attribute: null, from: t }) } if (t.removeAttribute(e), "is" === e) if (Be || Ge) try { At(t) } catch (e) {} else try { t.setAttribute(e, "") } catch (e) {} }, bt = function (e) { let t = null, n = null; if (He) e = "" + e; else { const t = T(e, /^[\r\n\t ]+/); n = t && t[0] } "application/xhtml+xml" === mt && rt === ot && (e = '
' + e + ""); const o = le ? le.createHTML(e) : e; if (rt === ot) try { t = (new Y).parseFromString(o, mt) } catch (e) {} if (!t || !t.documentElement) { t = se.createDocument(rt, "template", null); try { t.documentElement.innerHTML = it ? ce : o } catch (e) {} } const i = t.body || t.documentElement; return e && n && i.insertBefore(r.createTextNode(n), i.childNodes[0] || null), rt === ot ? pe.call(t, Pe ? "html" : "body")[0] : Pe ? t.documentElement : i }, St = function (e) { return ue.call(e.ownerDocument || e, e, B.SHOW_ELEMENT | B.SHOW_COMMENT | B.SHOW_TEXT | B.SHOW_PROCESSING_INSTRUCTION | B.SHOW_CDATA_SECTION, null) }, Nt = function (e) { return e instanceof W && ("string" != typeof e.nodeName || "string" != typeof e.textContent || "function" != typeof e.removeChild || !(e.attributes instanceof G) || "function" != typeof e.removeAttribute || "function" != typeof e.setAttribute || "string" != typeof e.namespaceURI || "function" != typeof e.insertBefore || "function" != typeof e.hasChildNodes) }, wt = function (e) { return "function" == typeof w && e instanceof w }; function Rt(e, t, n) { u(e, (e => { e.call(o, t, n, dt) })) } const Dt = function (e) { let t = null; if (Rt(de.beforeSanitizeElements, e, null), Nt(e)) return At(e), !0; const n = ft(e.nodeName); if (Rt(de.uponSanitizeElement, e, { tagName: n, allowedTags: Ne }), ze && e.hasChildNodes() && !wt(e.firstElementChild) && b(/<[/\w!]/g, e.innerHTML) && b(/<[/\w!]/g, e.textContent)) return At(e), !0; if (e.nodeType === ee) return At(e), !0; if (ze && e.nodeType === te && b(/<[/\w]/g, e.data)) return At(e), !0; if (!(xe.tagCheck instanceof Function && xe.tagCheck(n)) && (!Ne[n] || ve[n])) { if (!ve[n] && vt(n)) { if (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, n)) return !1; if (Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(n)) return !1 } if (Xe && !Ke[n]) { const t = ae(e) || e.parentNode, n = ie(e) || e.childNodes; if (n && t) { for (let o = n.length - 1; o >= 0; --o) { const r = $(n[o], !0); r.__removalCount = (e.__removalCount || 0) + 1, t.insertBefore(r, re(e)) } } } return At(e), !0 } return e instanceof D && !function (e) { let t = ae(e); t && t.tagName || (t = { namespaceURI: rt, tagName: "template" }); const n = h(e.tagName), o = h(t.tagName); return !!at[e.namespaceURI] && (e.namespaceURI === nt ? t.namespaceURI === ot ? "svg" === n : t.namespaceURI === tt ? "svg" === n && ("annotation-xml" === o || ct[o]) : Boolean(yt[n]) : e.namespaceURI === tt ? t.namespaceURI === ot ? "math" === n : t.namespaceURI === nt ? "math" === n && st[o] : Boolean(Et[n]) : e.namespaceURI === ot ? !(t.namespaceURI === nt && !st[o]) && !(t.namespaceURI === tt && !ct[o]) && !Et[n] && (ut[n] || !yt[n]) : !("application/xhtml+xml" !== mt || !at[e.namespaceURI])) }(e) ? (At(e), !0) : "noscript" !== n && "noembed" !== n && "noframes" !== n || !b(/<\/no(script|embed|frames)/i, e.innerHTML) ? (Ue && e.nodeType === Q && (t = e.textContent, u([he, ge, Te], (e => { t = y(t, e, " ") })), e.textContent !== t && (f(o.removed, { element: e.cloneNode() }), e.textContent = t)), Rt(de.afterSanitizeElements, e, null), !1) : (At(e), !0) }, Ct = function (e, t, n) { if (Ye && ("id" === t || "name" === t) && (n in r || n in ht)) return !1; if (ke && !Oe[t] && b(ye, t)); else if (Le && b(Ee, t)); else if (xe.attributeCheck instanceof Function && xe.attributeCheck(t, e)); else if (!Re[t] || Oe[t]) { if (!(vt(e) && (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, e) || Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(e)) && (Ce.attributeNameCheck instanceof RegExp && b(Ce.attributeNameCheck, t) || Ce.attributeNameCheck instanceof Function && Ce.attributeNameCheck(t, e)) || "is" === t && Ce.allowCustomizedBuiltInElements && (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, n) || Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(n)))) return !1 } else if (Qe[t]); else if (b(Se, y(n, _e, ""))); else if ("src" !== t && "xlink:href" !== t && "href" !== t || "script" === e || 0 !== E(n, "data:") || !Ze[e]) { if (Ie && !b(Ae, y(n, _e, ""))); else if (n) return !1 } else; return !0 }, vt = function (e) { return "annotation-xml" !== e && T(e, be) }, Ot = function (e) { Rt(de.beforeSanitizeAttributes, e, null); const { attributes: t } = e; if (!t || Nt(e)) return; const n = { attrName: "", attrValue: "", keepAttr: !0, allowedAttributes: Re, forceKeepAttr: void 0 }; let r = t.length; for (; r--;) { const i = t[r], { name: a, namespaceURI: l, value: c } = i, s = ft(a), m = c; let f = "value" === a ? m : A(m); if (n.attrName = s, n.attrValue = f, n.keepAttr = !0, n.forceKeepAttr = void 0, Rt(de.uponSanitizeAttribute, e, n), f = n.attrValue, !je || "id" !== s && "name" !== s || (_t(a, e), f = "user-content-" + f), ze && b(/((--!?|])>)|<\/(style|title|textarea)/i, f)) { _t(a, e); continue } if ("attributename" === s && T(f, "href")) { _t(a, e); continue } if (n.forceKeepAttr) continue; if (!n.keepAttr) { _t(a, e); continue } if (!Me && b(/\/>/i, f)) { _t(a, e); continue } Ue && u([he, ge, Te], (e => { f = y(f, e, " ") })); const d = ft(e.nodeName); if (Ct(d, s, f)) { if (le && "object" == typeof j && "function" == typeof j.getAttributeType) if (l); else switch (j.getAttributeType(d, s)) { case "TrustedHTML": f = le.createHTML(f); break; case "TrustedScriptURL": f = le.createScriptURL(f) }if (f !== m) try { l ? e.setAttributeNS(l, a, f) : e.setAttribute(a, f), Nt(e) ? At(e) : p(o.removed) } catch (t) { _t(a, e) } } else _t(a, e) } Rt(de.afterSanitizeAttributes, e, null) }, xt = function e(t) { let n = null; const o = St(t); for (Rt(de.beforeSanitizeShadowDOM, t, null); n = o.nextNode();)Rt(de.uponSanitizeShadowNode, n, null), Dt(n), Ot(n), n.content instanceof s && e(n.content); Rt(de.afterSanitizeShadowDOM, t, null) }; return o.sanitize = function (e) { let t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, n = null, r = null, i = null, l = null; if (it = !e, it && (e = "\x3c!--\x3e"), "string" != typeof e && !wt(e)) { if ("function" != typeof e.toString) throw S("toString is not a function"); if ("string" != typeof (e = e.toString())) throw S("dirty is not a string, aborting") } if (!o.isSupported) return e; if (Fe || Tt(t), o.removed = [], "string" == typeof e && (qe = !1), qe) { if (e.nodeName) { const t = ft(e.nodeName); if (!Ne[t] || ve[t]) throw S("root node is forbidden and cannot be sanitized in-place") } } else if (e instanceof w) n = bt("\x3c!----\x3e"), r = n.ownerDocument.importNode(e, !0), r.nodeType === J && "BODY" === r.nodeName || "HTML" === r.nodeName ? n = r : n.appendChild(r); else { if (!Be && !Ue && !Pe && -1 === e.indexOf("<")) return le && We ? le.createHTML(e) : e; if (n = bt(e), !n) return Be ? null : We ? ce : "" } n && He && At(n.firstChild); const c = St(qe ? e : n); for (; i = c.nextNode();)Dt(i), Ot(i), i.content instanceof s && xt(i.content); if (qe) return e; if (Be) { if (Ge) for (l = me.call(n.ownerDocument); n.firstChild;)l.appendChild(n.firstChild); else l = n; return (Re.shadowroot || Re.shadowrootmode) && (l = fe.call(a, l, !0)), l } let m = Pe ? n.outerHTML : n.innerHTML; return Pe && Ne["!doctype"] && n.ownerDocument && n.ownerDocument.doctype && n.ownerDocument.doctype.name && b(K, n.ownerDocument.doctype.name) && (m = "\n" + m), Ue && u([he, ge, Te], (e => { m = y(m, e, " ") })), le && We ? le.createHTML(m) : m }, o.setConfig = function () { Tt(arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}), Fe = !0 }, o.clearConfig = function () { dt = null, Fe = !1 }, o.isValidAttribute = function (e, t, n) { dt || Tt({}); const o = ft(e), r = ft(t); return Ct(o, r, n) }, o.addHook = function (e, t) { "function" == typeof t && f(de[e], t) }, o.removeHook = function (e, t) { if (void 0 !== t) { const n = m(de[e], t); return -1 === n ? void 0 : d(de[e], n, 1)[0] } return p(de[e]) }, o.removeHooks = function (e) { de[e] = [] }, o.removeAllHooks = function () { de = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] } }, o }(); return re }));
+ var Xt = Object.defineProperty; var c = (r, o) => Xt(r, "name", { value: o, configurable: !0 }); var { entries: gt, setPrototypeOf: ft, isFrozen: jt, getPrototypeOf: Vt, getOwnPropertyDescriptor: $t } = Object, { freeze: R, seal: b, create: ve } = Object, { apply: ke, construct: Ue } = typeof Reflect < "u" && Reflect; R || (R = c(function (o) { return o }, "freeze")); b || (b = c(function (o) { return o }, "seal")); ke || (ke = c(function (o, l) { for (var a = arguments.length, f = new Array(a > 2 ? a - 2 : 0), y = 2; y < a; y++)f[y - 2] = arguments[y]; return o.apply(l, f) }, "apply")); Ue || (Ue = c(function (o) { for (var l = arguments.length, a = new Array(l > 1 ? l - 1 : 0), f = 1; f < l; f++)a[f - 1] = arguments[f]; return new o(...a) }, "construct")); var fe = O(Array.prototype.forEach), qt = O(Array.prototype.lastIndexOf), ut = O(Array.prototype.pop), K = O(Array.prototype.push), Kt = O(Array.prototype.splice), me = O(String.prototype.toLowerCase), Ie = O(String.prototype.toString), Ce = O(String.prototype.match), Z = O(String.prototype.replace), Zt = O(String.prototype.indexOf), Jt = O(String.prototype.trim), D = O(Object.prototype.hasOwnProperty), S = O(RegExp.prototype.test), J = Qt(TypeError); function O(r) { return function (o) { o instanceof RegExp && (o.lastIndex = 0); for (var l = arguments.length, a = new Array(l > 1 ? l - 1 : 0), f = 1; f < l; f++)a[f - 1] = arguments[f]; return ke(r, o, a) } } c(O, "unapply"); function Qt(r) { return function () { for (var o = arguments.length, l = new Array(o), a = 0; a < o; a++)l[a] = arguments[a]; return Ue(r, l) } } c(Qt, "unconstruct"); function s(r, o) { let l = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : me; ft && ft(r, null); let a = o.length; for (; a--;) { let f = o[a]; if (typeof f == "string") { let y = l(f); y !== f && (jt(o) || (o[a] = y), f = y) } r[f] = !0 } return r } c(s, "addToSet"); function en(r) { for (let o = 0; o < r.length; o++)D(r, o) || (r[o] = null); return r } c(en, "cleanArray"); function w(r) { let o = ve(null); for (let [l, a] of gt(r)) D(r, l) && (Array.isArray(a) ? o[l] = en(a) : a && typeof a == "object" && a.constructor === Object ? o[l] = w(a) : o[l] = a); return o } c(w, "clone"); function Q(r, o) { for (; r !== null;) { let a = $t(r, o); if (a) { if (a.get) return O(a.get); if (typeof a.value == "function") return O(a.value) } r = Vt(r) } function l() { return null } return c(l, "fallbackValue"), l } c(Q, "lookupGetter"); var mt = R(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), Me = R(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), we = R(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), tn = R(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), xe = R(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), nn = R(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), pt = R(["#text"]), dt = R(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]), Pe = R(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), Tt = R(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), ue = R(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), on = b(/\{\{[\w\W]*|[\w\W]*\}\}/gm), an = b(/<%[\w\W]*|[\w\W]*%>/gm), rn = b(/\$\{[\w\W]*/gm), sn = b(/^data-[\-\w.\u00B7-\uFFFF]+$/), ln = b(/^aria-[\-\w]+$/), ht = b(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i), cn = b(/^(?:\w+script|data):/i), fn = b(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g), At = b(/^html$/i), un = b(/^[a-z][.\w]*(-[.\w]+)+$/i), Et = Object.freeze({ __proto__: null, ARIA_ATTR: ln, ATTR_WHITESPACE: fn, CUSTOM_ELEMENT: un, DATA_ATTR: sn, DOCTYPE_NAME: At, ERB_EXPR: an, IS_ALLOWED_URI: ht, IS_SCRIPT_OR_DATA: cn, MUSTACHE_EXPR: on, TMPLIT_EXPR: rn }), ee = { element: 1, attribute: 2, text: 3, cdataSection: 4, entityReference: 5, entityNode: 6, progressingInstruction: 7, comment: 8, document: 9, documentType: 10, documentFragment: 11, notation: 12 }, mn = c(function () { return typeof window > "u" ? null : window }, "getGlobal"), pn = c(function (o, l) { if (typeof o != "object" || typeof o.createPolicy != "function") return null; let a = null, f = "data-tt-policy-suffix"; l && l.hasAttribute(f) && (a = l.getAttribute(f)); let y = "dompurify" + (a ? "#" + a : ""); try { return o.createPolicy(y, { createHTML(v) { return v }, createScriptURL(v) { return v } }) } catch { return console.warn("TrustedTypes policy " + y + " could not be created."), null } }, "_createTrustedTypesPolicy"), _t = c(function () { return { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] } }, "_createHooksMap"); function St() { let r = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : mn(), o = c(i => St(i), "DOMPurify"); if (o.version = "3.3.0", o.removed = [], !r || !r.document || r.document.nodeType !== ee.document || !r.Element) return o.isSupported = !1, o; let { document: l } = r, a = l, f = a.currentScript, { DocumentFragment: y, HTMLTemplateElement: v, Node: pe, Element: Fe, NodeFilter: Y, NamedNodeMap: Rt = r.NamedNodeMap || r.MozNamedAttrMap, HTMLFormElement: Ot, DOMParser: yt, trustedTypes: te } = r, X = Fe.prototype, Lt = Q(X, "cloneNode"), bt = Q(X, "remove"), Dt = Q(X, "nextSibling"), Nt = Q(X, "childNodes"), ne = Q(X, "parentNode"); if (typeof v == "function") { let i = l.createElement("template"); i.content && i.content.ownerDocument && (l = i.content.ownerDocument) } let h, j = "", { implementation: de, createNodeIterator: It, createDocumentFragment: Ct, getElementsByTagName: Mt } = l, { importNode: wt } = a, A = _t(); o.isSupported = typeof gt == "function" && typeof ne == "function" && de && de.createHTMLDocument !== void 0; let { MUSTACHE_EXPR: Te, ERB_EXPR: Ee, TMPLIT_EXPR: _e, DATA_ATTR: xt, ARIA_ATTR: Pt, IS_SCRIPT_OR_DATA: vt, ATTR_WHITESPACE: He, CUSTOM_ELEMENT: kt } = Et, { IS_ALLOWED_URI: ze } = Et, d = null, Ge = s({}, [...mt, ...Me, ...we, ...xe, ...pt]), E = null, We = s({}, [...dt, ...Pe, ...Tt, ...ue]), m = Object.seal(ve(null, { tagNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, allowCustomizedBuiltInElements: { writable: !0, configurable: !1, enumerable: !0, value: !1 } })), V = null, ge = null, k = Object.seal(ve(null, { tagCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeCheck: { writable: !0, configurable: !1, enumerable: !0, value: null } })), Be = !0, he = !0, Ye = !1, Xe = !0, U = !1, oe = !0, x = !1, Ae = !1, Se = !1, F = !1, ie = !1, ae = !1, je = !0, Ve = !1, Ut = "user-content-", Re = !0, $ = !1, H = {}, z = null, $e = s({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]), qe = null, Ke = s({}, ["audio", "video", "img", "source", "image", "track"]), Oe = null, Ze = s({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), re = "http://www.w3.org/1998/Math/MathML", se = "http://www.w3.org/2000/svg", I = "http://www.w3.org/1999/xhtml", G = I, ye = !1, Le = null, Ft = s({}, [re, se, I], Ie), le = s({}, ["mi", "mo", "mn", "ms", "mtext"]), ce = s({}, ["annotation-xml"]), Ht = s({}, ["title", "style", "font", "a", "script"]), q = null, zt = ["application/xhtml+xml", "text/html"], Gt = "text/html", T = null, W = null, Wt = l.createElement("form"), Je = c(function (e) { return e instanceof RegExp || e instanceof Function }, "isRegexOrFunction"), be = c(function () { let e = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; if (!(W && W === e)) { if ((!e || typeof e != "object") && (e = {}), e = w(e), q = zt.indexOf(e.PARSER_MEDIA_TYPE) === -1 ? Gt : e.PARSER_MEDIA_TYPE, T = q === "application/xhtml+xml" ? Ie : me, d = D(e, "ALLOWED_TAGS") ? s({}, e.ALLOWED_TAGS, T) : Ge, E = D(e, "ALLOWED_ATTR") ? s({}, e.ALLOWED_ATTR, T) : We, Le = D(e, "ALLOWED_NAMESPACES") ? s({}, e.ALLOWED_NAMESPACES, Ie) : Ft, Oe = D(e, "ADD_URI_SAFE_ATTR") ? s(w(Ze), e.ADD_URI_SAFE_ATTR, T) : Ze, qe = D(e, "ADD_DATA_URI_TAGS") ? s(w(Ke), e.ADD_DATA_URI_TAGS, T) : Ke, z = D(e, "FORBID_CONTENTS") ? s({}, e.FORBID_CONTENTS, T) : $e, V = D(e, "FORBID_TAGS") ? s({}, e.FORBID_TAGS, T) : w({}), ge = D(e, "FORBID_ATTR") ? s({}, e.FORBID_ATTR, T) : w({}), H = D(e, "USE_PROFILES") ? e.USE_PROFILES : !1, Be = e.ALLOW_ARIA_ATTR !== !1, he = e.ALLOW_DATA_ATTR !== !1, Ye = e.ALLOW_UNKNOWN_PROTOCOLS || !1, Xe = e.ALLOW_SELF_CLOSE_IN_ATTR !== !1, U = e.SAFE_FOR_TEMPLATES || !1, oe = e.SAFE_FOR_XML !== !1, x = e.WHOLE_DOCUMENT || !1, F = e.RETURN_DOM || !1, ie = e.RETURN_DOM_FRAGMENT || !1, ae = e.RETURN_TRUSTED_TYPE || !1, Se = e.FORCE_BODY || !1, je = e.SANITIZE_DOM !== !1, Ve = e.SANITIZE_NAMED_PROPS || !1, Re = e.KEEP_CONTENT !== !1, $ = e.IN_PLACE || !1, ze = e.ALLOWED_URI_REGEXP || ht, G = e.NAMESPACE || I, le = e.MATHML_TEXT_INTEGRATION_POINTS || le, ce = e.HTML_INTEGRATION_POINTS || ce, m = e.CUSTOM_ELEMENT_HANDLING || {}, e.CUSTOM_ELEMENT_HANDLING && Je(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck) && (m.tagNameCheck = e.CUSTOM_ELEMENT_HANDLING.tagNameCheck), e.CUSTOM_ELEMENT_HANDLING && Je(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) && (m.attributeNameCheck = e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck), e.CUSTOM_ELEMENT_HANDLING && typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements == "boolean" && (m.allowCustomizedBuiltInElements = e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements), U && (he = !1), ie && (F = !0), H && (d = s({}, pt), E = [], H.html === !0 && (s(d, mt), s(E, dt)), H.svg === !0 && (s(d, Me), s(E, Pe), s(E, ue)), H.svgFilters === !0 && (s(d, we), s(E, Pe), s(E, ue)), H.mathMl === !0 && (s(d, xe), s(E, Tt), s(E, ue))), e.ADD_TAGS && (typeof e.ADD_TAGS == "function" ? k.tagCheck = e.ADD_TAGS : (d === Ge && (d = w(d)), s(d, e.ADD_TAGS, T))), e.ADD_ATTR && (typeof e.ADD_ATTR == "function" ? k.attributeCheck = e.ADD_ATTR : (E === We && (E = w(E)), s(E, e.ADD_ATTR, T))), e.ADD_URI_SAFE_ATTR && s(Oe, e.ADD_URI_SAFE_ATTR, T), e.FORBID_CONTENTS && (z === $e && (z = w(z)), s(z, e.FORBID_CONTENTS, T)), Re && (d["#text"] = !0), x && s(d, ["html", "head", "body"]), d.table && (s(d, ["tbody"]), delete V.tbody), e.TRUSTED_TYPES_POLICY) { if (typeof e.TRUSTED_TYPES_POLICY.createHTML != "function") throw J('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); if (typeof e.TRUSTED_TYPES_POLICY.createScriptURL != "function") throw J('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); h = e.TRUSTED_TYPES_POLICY, j = h.createHTML("") } else h === void 0 && (h = pn(te, f)), h !== null && typeof j == "string" && (j = h.createHTML("")); R && R(e), W = e } }, "_parseConfig"), Qe = s({}, [...Me, ...we, ...tn]), et = s({}, [...xe, ...nn]), Bt = c(function (e) { let t = ne(e); (!t || !t.tagName) && (t = { namespaceURI: G, tagName: "template" }); let n = me(e.tagName), u = me(t.tagName); return Le[e.namespaceURI] ? e.namespaceURI === se ? t.namespaceURI === I ? n === "svg" : t.namespaceURI === re ? n === "svg" && (u === "annotation-xml" || le[u]) : !!Qe[n] : e.namespaceURI === re ? t.namespaceURI === I ? n === "math" : t.namespaceURI === se ? n === "math" && ce[u] : !!et[n] : e.namespaceURI === I ? t.namespaceURI === se && !ce[u] || t.namespaceURI === re && !le[u] ? !1 : !et[n] && (Ht[n] || !Qe[n]) : !!(q === "application/xhtml+xml" && Le[e.namespaceURI]) : !1 }, "_checkValidNamespace"), N = c(function (e) { K(o.removed, { element: e }); try { ne(e).removeChild(e) } catch { bt(e) } }, "_forceRemove"), P = c(function (e, t) { try { K(o.removed, { attribute: t.getAttributeNode(e), from: t }) } catch { K(o.removed, { attribute: null, from: t }) } if (t.removeAttribute(e), e === "is") if (F || ie) try { N(t) } catch {} else try { t.setAttribute(e, "") } catch {} }, "_removeAttribute"), tt = c(function (e) { let t = null, n = null; if (Se) e = "" + e; else { let p = Ce(e, /^[\r\n\t ]+/); n = p && p[0] } q === "application/xhtml+xml" && G === I && (e = '' + e + ""); let u = h ? h.createHTML(e) : e; if (G === I) try { t = new yt().parseFromString(u, q) } catch {} if (!t || !t.documentElement) { t = de.createDocument(G, "template", null); try { t.documentElement.innerHTML = ye ? j : u } catch {} } let g = t.body || t.documentElement; return e && n && g.insertBefore(l.createTextNode(n), g.childNodes[0] || null), G === I ? Mt.call(t, x ? "html" : "body")[0] : x ? t.documentElement : g }, "_initDocument"), nt = c(function (e) { return It.call(e.ownerDocument || e, e, Y.SHOW_ELEMENT | Y.SHOW_COMMENT | Y.SHOW_TEXT | Y.SHOW_PROCESSING_INSTRUCTION | Y.SHOW_CDATA_SECTION, null) }, "_createNodeIterator"), De = c(function (e) { return e instanceof Ot && (typeof e.nodeName != "string" || typeof e.textContent != "string" || typeof e.removeChild != "function" || !(e.attributes instanceof Rt) || typeof e.removeAttribute != "function" || typeof e.setAttribute != "function" || typeof e.namespaceURI != "string" || typeof e.insertBefore != "function" || typeof e.hasChildNodes != "function") }, "_isClobbered"), ot = c(function (e) { return typeof pe == "function" && e instanceof pe }, "_isNode"); function C(i, e, t) { fe(i, n => { n.call(o, e, t, W) }) } c(C, "_executeHooks"); let it = c(function (e) { let t = null; if (C(A.beforeSanitizeElements, e, null), De(e)) return N(e), !0; let n = T(e.nodeName); if (C(A.uponSanitizeElement, e, { tagName: n, allowedTags: d }), oe && e.hasChildNodes() && !ot(e.firstElementChild) && S(/<[/\w!]/g, e.innerHTML) && S(/<[/\w!]/g, e.textContent) || e.nodeType === ee.progressingInstruction || oe && e.nodeType === ee.comment && S(/<[/\w]/g, e.data)) return N(e), !0; if (!(k.tagCheck instanceof Function && k.tagCheck(n)) && (!d[n] || V[n])) { if (!V[n] && rt(n) && (m.tagNameCheck instanceof RegExp && S(m.tagNameCheck, n) || m.tagNameCheck instanceof Function && m.tagNameCheck(n))) return !1; if (Re && !z[n]) { let u = ne(e) || e.parentNode, g = Nt(e) || e.childNodes; if (g && u) { let p = g.length; for (let L = p - 1; L >= 0; --L) { let M = Lt(g[L], !0); M.__removalCount = (e.__removalCount || 0) + 1, u.insertBefore(M, Dt(e)) } } } return N(e), !0 } return e instanceof Fe && !Bt(e) || (n === "noscript" || n === "noembed" || n === "noframes") && S(/<\/no(script|embed|frames)/i, e.innerHTML) ? (N(e), !0) : (U && e.nodeType === ee.text && (t = e.textContent, fe([Te, Ee, _e], u => { t = Z(t, u, " ") }), e.textContent !== t && (K(o.removed, { element: e.cloneNode() }), e.textContent = t)), C(A.afterSanitizeElements, e, null), !1) }, "_sanitizeElements"), at = c(function (e, t, n) { if (je && (t === "id" || t === "name") && (n in l || n in Wt)) return !1; if (!(he && !ge[t] && S(xt, t))) { if (!(Be && S(Pt, t))) { if (!(k.attributeCheck instanceof Function && k.attributeCheck(t, e))) { if (!E[t] || ge[t]) { if (!(rt(e) && (m.tagNameCheck instanceof RegExp && S(m.tagNameCheck, e) || m.tagNameCheck instanceof Function && m.tagNameCheck(e)) && (m.attributeNameCheck instanceof RegExp && S(m.attributeNameCheck, t) || m.attributeNameCheck instanceof Function && m.attributeNameCheck(t, e)) || t === "is" && m.allowCustomizedBuiltInElements && (m.tagNameCheck instanceof RegExp && S(m.tagNameCheck, n) || m.tagNameCheck instanceof Function && m.tagNameCheck(n)))) return !1 } else if (!Oe[t]) { if (!S(ze, Z(n, He, ""))) { if (!((t === "src" || t === "xlink:href" || t === "href") && e !== "script" && Zt(n, "data:") === 0 && qe[e])) { if (!(Ye && !S(vt, Z(n, He, "")))) { if (n) return !1 } } } } } } } return !0 }, "_isValidAttribute"), rt = c(function (e) { return e !== "annotation-xml" && Ce(e, kt) }, "_isBasicCustomElement"), st = c(function (e) { C(A.beforeSanitizeAttributes, e, null); let { attributes: t } = e; if (!t || De(e)) return; let n = { attrName: "", attrValue: "", keepAttr: !0, allowedAttributes: E, forceKeepAttr: void 0 }, u = t.length; for (; u--;) { let g = t[u], { name: p, namespaceURI: L, value: M } = g, B = T(p), Ne = M, _ = p === "value" ? Ne : Jt(Ne); if (n.attrName = B, n.attrValue = _, n.keepAttr = !0, n.forceKeepAttr = void 0, C(A.uponSanitizeAttribute, e, n), _ = n.attrValue, Ve && (B === "id" || B === "name") && (P(p, e), _ = Ut + _), oe && S(/((--!?|])>)|<\/(style|title|textarea)/i, _)) { P(p, e); continue } if (B === "attributename" && Ce(_, "href")) { P(p, e); continue } if (n.forceKeepAttr) continue; if (!n.keepAttr) { P(p, e); continue } if (!Xe && S(/\/>/i, _)) { P(p, e); continue } U && fe([Te, Ee, _e], ct => { _ = Z(_, ct, " ") }); let lt = T(e.nodeName); if (!at(lt, B, _)) { P(p, e); continue } if (h && typeof te == "object" && typeof te.getAttributeType == "function" && !L) switch (te.getAttributeType(lt, B)) { case "TrustedHTML": { _ = h.createHTML(_); break } case "TrustedScriptURL": { _ = h.createScriptURL(_); break } }if (_ !== Ne) try { L ? e.setAttributeNS(L, p, _) : e.setAttribute(p, _), De(e) ? N(e) : ut(o.removed) } catch { P(p, e) } } C(A.afterSanitizeAttributes, e, null) }, "_sanitizeAttributes"), Yt = c(function i(e) { let t = null, n = nt(e); for (C(A.beforeSanitizeShadowDOM, e, null); t = n.nextNode();)C(A.uponSanitizeShadowNode, t, null), it(t), st(t), t.content instanceof y && i(t.content); C(A.afterSanitizeShadowDOM, e, null) }, "_sanitizeShadowDOM"); return o.sanitize = function (i) { let e = arguments.length > 1 && arguments[1] !== void 0 ? arguments[1] : {}, t = null, n = null, u = null, g = null; if (ye = !i, ye && (i = ""), typeof i != "string" && !ot(i)) if (typeof i.toString == "function") { if (i = i.toString(), typeof i != "string") throw J("dirty is not a string, aborting") } else throw J("toString is not a function"); if (!o.isSupported) return i; if (Ae || be(e), o.removed = [], typeof i == "string" && ($ = !1), $) { if (i.nodeName) { let M = T(i.nodeName); if (!d[M] || V[M]) throw J("root node is forbidden and cannot be sanitized in-place") } } else if (i instanceof pe) t = tt(""), n = t.ownerDocument.importNode(i, !0), n.nodeType === ee.element && n.nodeName === "BODY" || n.nodeName === "HTML" ? t = n : t.appendChild(n); else { if (!F && !U && !x && i.indexOf("<") === -1) return h && ae ? h.createHTML(i) : i; if (t = tt(i), !t) return F ? null : ae ? j : "" } t && Se && N(t.firstChild); let p = nt($ ? i : t); for (; u = p.nextNode();)it(u), st(u), u.content instanceof y && Yt(u.content); if ($) return i; if (F) { if (ie) for (g = Ct.call(t.ownerDocument); t.firstChild;)g.appendChild(t.firstChild); else g = t; return (E.shadowroot || E.shadowrootmode) && (g = wt.call(a, g, !0)), g } let L = x ? t.outerHTML : t.innerHTML; return x && d["!doctype"] && t.ownerDocument && t.ownerDocument.doctype && t.ownerDocument.doctype.name && S(At, t.ownerDocument.doctype.name) && (L = "` + L), U && fe([Te, Ee, _e], M => { L = Z(L, M, " ") }), h && ae ? h.createHTML(L) : L }, o.setConfig = function () { let i = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : {}; be(i), Ae = !0 }, o.clearConfig = function () { W = null, Ae = !1 }, o.isValidAttribute = function (i, e, t) { W || be({}); let n = T(i), u = T(e); return at(n, u, t) }, o.addHook = function (i, e) { typeof e == "function" && K(A[i], e) }, o.removeHook = function (i, e) { if (e !== void 0) { let t = qt(A[i], e); return t === -1 ? void 0 : Kt(A[i], t, 1)[0] } return ut(A[i]) }, o.removeHooks = function (i) { A[i] = [] }, o.removeAllHooks = function () { A = _t() }, o } c(St, "createDOMPurify"); var DOMPurify = St();
// Create a custom CSP policy (using DOM Purify for added security)
let goodTube_csp = false;
diff --git a/goodtube.min.js b/goodtube.min.js
index 0bbd150..b573d33 100644
--- a/goodtube.min.js
+++ b/goodtube.min.js
@@ -1,303 +1,8 @@
-(function () {
- 'use strict';
-
-
- /* Setup the CSP (content security policy)
- ------------------------------------------------------------------------------------------ */
- // Include DOM Purify (https://github.com/cure53/DOMPurify/blob/main/dist/purify.min.js)
- // We're placing this inline this because fetching it from an external repo which don't have control over is likely unreliable in the long term
- !function (e, t) { "object" == typeof exports && "undefined" != typeof module ? module.exports = t() : "function" == typeof define && define.amd ? define(t) : (e = "undefined" != typeof globalThis ? globalThis : e || self).DOMPurify = t() }(this, (function () { "use strict"; const { entries: e, setPrototypeOf: t, isFrozen: n, getPrototypeOf: o, getOwnPropertyDescriptor: r } = Object; let { freeze: i, seal: a, create: l } = Object, { apply: c, construct: s } = "undefined" != typeof Reflect && Reflect; i || (i = function (e) { return e }), a || (a = function (e) { return e }), c || (c = function (e, t) { for (var n = arguments.length, o = new Array(n > 2 ? n - 2 : 0), r = 2; r < n; r++)o[r - 2] = arguments[r]; return e.apply(t, o) }), s || (s = function (e) { for (var t = arguments.length, n = new Array(t > 1 ? t - 1 : 0), o = 1; o < t; o++)n[o - 1] = arguments[o]; return new e(...n) }); const u = w(Array.prototype.forEach), m = w(Array.prototype.lastIndexOf), p = w(Array.prototype.pop), f = w(Array.prototype.push), d = w(Array.prototype.splice), h = w(String.prototype.toLowerCase), g = w(String.prototype.toString), T = w(String.prototype.match), y = w(String.prototype.replace), E = w(String.prototype.indexOf), A = w(String.prototype.trim), _ = w(Object.prototype.hasOwnProperty), b = w(RegExp.prototype.test), S = (N = TypeError, function () { for (var e = arguments.length, t = new Array(e), n = 0; n < e; n++)t[n] = arguments[n]; return s(N, t) }); var N; function w(e) { return function (t) { t instanceof RegExp && (t.lastIndex = 0); for (var n = arguments.length, o = new Array(n > 1 ? n - 1 : 0), r = 1; r < n; r++)o[r - 1] = arguments[r]; return c(e, t, o) } } function R(e, o) { let r = arguments.length > 2 && void 0 !== arguments[2] ? arguments[2] : h; t && t(e, null); let i = o.length; for (; i--;) { let t = o[i]; if ("string" == typeof t) { const e = r(t); e !== t && (n(o) || (o[i] = e), t = e) } e[t] = !0 } return e } function D(e) { for (let t = 0; t < e.length; t++) { _(e, t) || (e[t] = null) } return e } function C(t) { const n = l(null); for (const [o, r] of e(t)) { _(t, o) && (Array.isArray(r) ? n[o] = D(r) : r && "object" == typeof r && r.constructor === Object ? n[o] = C(r) : n[o] = r) } return n } function v(e, t) { for (; null !== e;) { const n = r(e, t); if (n) { if (n.get) return w(n.get); if ("function" == typeof n.value) return w(n.value) } e = o(e) } return function () { return null } } const O = i(["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "bdi", "bdo", "big", "blink", "blockquote", "body", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "content", "data", "datalist", "dd", "decorator", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "element", "em", "fieldset", "figcaption", "figure", "font", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "marquee", "menu", "menuitem", "meter", "nav", "nobr", "ol", "optgroup", "option", "output", "p", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "search", "section", "select", "shadow", "slot", "small", "source", "spacer", "span", "strike", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"]), x = i(["svg", "a", "altglyph", "altglyphdef", "altglyphitem", "animatecolor", "animatemotion", "animatetransform", "circle", "clippath", "defs", "desc", "ellipse", "enterkeyhint", "exportparts", "filter", "font", "g", "glyph", "glyphref", "hkern", "image", "inputmode", "line", "lineargradient", "marker", "mask", "metadata", "mpath", "part", "path", "pattern", "polygon", "polyline", "radialgradient", "rect", "stop", "style", "switch", "symbol", "text", "textpath", "title", "tref", "tspan", "view", "vkern"]), L = i(["feBlend", "feColorMatrix", "feComponentTransfer", "feComposite", "feConvolveMatrix", "feDiffuseLighting", "feDisplacementMap", "feDistantLight", "feDropShadow", "feFlood", "feFuncA", "feFuncB", "feFuncG", "feFuncR", "feGaussianBlur", "feImage", "feMerge", "feMergeNode", "feMorphology", "feOffset", "fePointLight", "feSpecularLighting", "feSpotLight", "feTile", "feTurbulence"]), k = i(["animate", "color-profile", "cursor", "discard", "font-face", "font-face-format", "font-face-name", "font-face-src", "font-face-uri", "foreignobject", "hatch", "hatchpath", "mesh", "meshgradient", "meshpatch", "meshrow", "missing-glyph", "script", "set", "solidcolor", "unknown", "use"]), I = i(["math", "menclose", "merror", "mfenced", "mfrac", "mglyph", "mi", "mlabeledtr", "mmultiscripts", "mn", "mo", "mover", "mpadded", "mphantom", "mroot", "mrow", "ms", "mspace", "msqrt", "mstyle", "msub", "msup", "msubsup", "mtable", "mtd", "mtext", "mtr", "munder", "munderover", "mprescripts"]), M = i(["maction", "maligngroup", "malignmark", "mlongdiv", "mscarries", "mscarry", "msgroup", "mstack", "msline", "msrow", "semantics", "annotation", "annotation-xml", "mprescripts", "none"]), U = i(["#text"]), z = i(["accept", "action", "align", "alt", "autocapitalize", "autocomplete", "autopictureinpicture", "autoplay", "background", "bgcolor", "border", "capture", "cellpadding", "cellspacing", "checked", "cite", "class", "clear", "color", "cols", "colspan", "controls", "controlslist", "coords", "crossorigin", "datetime", "decoding", "default", "dir", "disabled", "disablepictureinpicture", "disableremoteplayback", "download", "draggable", "enctype", "enterkeyhint", "exportparts", "face", "for", "headers", "height", "hidden", "high", "href", "hreflang", "id", "inert", "inputmode", "integrity", "ismap", "kind", "label", "lang", "list", "loading", "loop", "low", "max", "maxlength", "media", "method", "min", "minlength", "multiple", "muted", "name", "nonce", "noshade", "novalidate", "nowrap", "open", "optimum", "part", "pattern", "placeholder", "playsinline", "popover", "popovertarget", "popovertargetaction", "poster", "preload", "pubdate", "radiogroup", "readonly", "rel", "required", "rev", "reversed", "role", "rows", "rowspan", "spellcheck", "scope", "selected", "shape", "size", "sizes", "slot", "span", "srclang", "start", "src", "srcset", "step", "style", "summary", "tabindex", "title", "translate", "type", "usemap", "valign", "value", "width", "wrap", "xmlns", "slot"]), P = i(["accent-height", "accumulate", "additive", "alignment-baseline", "amplitude", "ascent", "attributename", "attributetype", "azimuth", "basefrequency", "baseline-shift", "begin", "bias", "by", "class", "clip", "clippathunits", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cx", "cy", "d", "dx", "dy", "diffuseconstant", "direction", "display", "divisor", "dur", "edgemode", "elevation", "end", "exponent", "fill", "fill-opacity", "fill-rule", "filter", "filterunits", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "fx", "fy", "g1", "g2", "glyph-name", "glyphref", "gradientunits", "gradienttransform", "height", "href", "id", "image-rendering", "in", "in2", "intercept", "k", "k1", "k2", "k3", "k4", "kerning", "keypoints", "keysplines", "keytimes", "lang", "lengthadjust", "letter-spacing", "kernelmatrix", "kernelunitlength", "lighting-color", "local", "marker-end", "marker-mid", "marker-start", "markerheight", "markerunits", "markerwidth", "maskcontentunits", "maskunits", "max", "mask", "mask-type", "media", "method", "mode", "min", "name", "numoctaves", "offset", "operator", "opacity", "order", "orient", "orientation", "origin", "overflow", "paint-order", "path", "pathlength", "patterncontentunits", "patterntransform", "patternunits", "points", "preservealpha", "preserveaspectratio", "primitiveunits", "r", "rx", "ry", "radius", "refx", "refy", "repeatcount", "repeatdur", "restart", "result", "rotate", "scale", "seed", "shape-rendering", "slope", "specularconstant", "specularexponent", "spreadmethod", "startoffset", "stddeviation", "stitchtiles", "stop-color", "stop-opacity", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke", "stroke-width", "style", "surfacescale", "systemlanguage", "tabindex", "tablevalues", "targetx", "targety", "transform", "transform-origin", "text-anchor", "text-decoration", "text-rendering", "textlength", "type", "u1", "u2", "unicode", "values", "viewbox", "visibility", "version", "vert-adv-y", "vert-origin-x", "vert-origin-y", "width", "word-spacing", "wrap", "writing-mode", "xchannelselector", "ychannelselector", "x", "x1", "x2", "xmlns", "y", "y1", "y2", "z", "zoomandpan"]), F = i(["accent", "accentunder", "align", "bevelled", "close", "columnsalign", "columnlines", "columnspan", "denomalign", "depth", "dir", "display", "displaystyle", "encoding", "fence", "frame", "height", "href", "id", "largeop", "length", "linethickness", "lspace", "lquote", "mathbackground", "mathcolor", "mathsize", "mathvariant", "maxsize", "minsize", "movablelimits", "notation", "numalign", "open", "rowalign", "rowlines", "rowspacing", "rowspan", "rspace", "rquote", "scriptlevel", "scriptminsize", "scriptsizemultiplier", "selection", "separator", "separators", "stretchy", "subscriptshift", "supscriptshift", "symmetric", "voffset", "width", "xmlns"]), H = i(["xlink:href", "xml:id", "xlink:title", "xml:space", "xmlns:xlink"]), B = a(/\{\{[\w\W]*|[\w\W]*\}\}/gm), G = a(/<%[\w\W]*|[\w\W]*%>/gm), W = a(/\$\{[\w\W]*/gm), Y = a(/^data-[\-\w.\u00B7-\uFFFF]+$/), j = a(/^aria-[\-\w]+$/), X = a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i), q = a(/^(?:\w+script|data):/i), $ = a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g), K = a(/^html$/i), V = a(/^[a-z][.\w]*(-[.\w]+)+$/i); var Z = Object.freeze({ __proto__: null, ARIA_ATTR: j, ATTR_WHITESPACE: $, CUSTOM_ELEMENT: V, DATA_ATTR: Y, DOCTYPE_NAME: K, ERB_EXPR: G, IS_ALLOWED_URI: X, IS_SCRIPT_OR_DATA: q, MUSTACHE_EXPR: B, TMPLIT_EXPR: W }); const J = 1, Q = 3, ee = 7, te = 8, ne = 9, oe = function () { return "undefined" == typeof window ? null : window }; var re = function t() { let n = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : oe(); const o = e => t(e); if (o.version = "3.3.0", o.removed = [], !n || !n.document || n.document.nodeType !== ne || !n.Element) return o.isSupported = !1, o; let { document: r } = n; const a = r, c = a.currentScript, { DocumentFragment: s, HTMLTemplateElement: N, Node: w, Element: D, NodeFilter: B, NamedNodeMap: G = n.NamedNodeMap || n.MozNamedAttrMap, HTMLFormElement: W, DOMParser: Y, trustedTypes: j } = n, q = D.prototype, $ = v(q, "cloneNode"), V = v(q, "remove"), re = v(q, "nextSibling"), ie = v(q, "childNodes"), ae = v(q, "parentNode"); if ("function" == typeof N) { const e = r.createElement("template"); e.content && e.content.ownerDocument && (r = e.content.ownerDocument) } let le, ce = ""; const { implementation: se, createNodeIterator: ue, createDocumentFragment: me, getElementsByTagName: pe } = r, { importNode: fe } = a; let de = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] }; o.isSupported = "function" == typeof e && "function" == typeof ae && se && void 0 !== se.createHTMLDocument; const { MUSTACHE_EXPR: he, ERB_EXPR: ge, TMPLIT_EXPR: Te, DATA_ATTR: ye, ARIA_ATTR: Ee, IS_SCRIPT_OR_DATA: Ae, ATTR_WHITESPACE: _e, CUSTOM_ELEMENT: be } = Z; let { IS_ALLOWED_URI: Se } = Z, Ne = null; const we = R({}, [...O, ...x, ...L, ...I, ...U]); let Re = null; const De = R({}, [...z, ...P, ...F, ...H]); let Ce = Object.seal(l(null, { tagNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeNameCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, allowCustomizedBuiltInElements: { writable: !0, configurable: !1, enumerable: !0, value: !1 } })), ve = null, Oe = null; const xe = Object.seal(l(null, { tagCheck: { writable: !0, configurable: !1, enumerable: !0, value: null }, attributeCheck: { writable: !0, configurable: !1, enumerable: !0, value: null } })); let Le = !0, ke = !0, Ie = !1, Me = !0, Ue = !1, ze = !0, Pe = !1, Fe = !1, He = !1, Be = !1, Ge = !1, We = !1, Ye = !0, je = !1, Xe = !0, qe = !1, $e = {}, Ke = null; const Ve = R({}, ["annotation-xml", "audio", "colgroup", "desc", "foreignobject", "head", "iframe", "math", "mi", "mn", "mo", "ms", "mtext", "noembed", "noframes", "noscript", "plaintext", "script", "style", "svg", "template", "thead", "title", "video", "xmp"]); let Ze = null; const Je = R({}, ["audio", "video", "img", "source", "image", "track"]); let Qe = null; const et = R({}, ["alt", "class", "for", "id", "label", "name", "pattern", "placeholder", "role", "summary", "title", "value", "style", "xmlns"]), tt = "http://www.w3.org/1998/Math/MathML", nt = "http://www.w3.org/2000/svg", ot = "http://www.w3.org/1999/xhtml"; let rt = ot, it = !1, at = null; const lt = R({}, [tt, nt, ot], g); let ct = R({}, ["mi", "mo", "mn", "ms", "mtext"]), st = R({}, ["annotation-xml"]); const ut = R({}, ["title", "style", "font", "a", "script"]); let mt = null; const pt = ["application/xhtml+xml", "text/html"]; let ft = null, dt = null; const ht = r.createElement("form"), gt = function (e) { return e instanceof RegExp || e instanceof Function }, Tt = function () { let e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}; if (!dt || dt !== e) { if (e && "object" == typeof e || (e = {}), e = C(e), mt = -1 === pt.indexOf(e.PARSER_MEDIA_TYPE) ? "text/html" : e.PARSER_MEDIA_TYPE, ft = "application/xhtml+xml" === mt ? g : h, Ne = _(e, "ALLOWED_TAGS") ? R({}, e.ALLOWED_TAGS, ft) : we, Re = _(e, "ALLOWED_ATTR") ? R({}, e.ALLOWED_ATTR, ft) : De, at = _(e, "ALLOWED_NAMESPACES") ? R({}, e.ALLOWED_NAMESPACES, g) : lt, Qe = _(e, "ADD_URI_SAFE_ATTR") ? R(C(et), e.ADD_URI_SAFE_ATTR, ft) : et, Ze = _(e, "ADD_DATA_URI_TAGS") ? R(C(Je), e.ADD_DATA_URI_TAGS, ft) : Je, Ke = _(e, "FORBID_CONTENTS") ? R({}, e.FORBID_CONTENTS, ft) : Ve, ve = _(e, "FORBID_TAGS") ? R({}, e.FORBID_TAGS, ft) : C({}), Oe = _(e, "FORBID_ATTR") ? R({}, e.FORBID_ATTR, ft) : C({}), $e = !!_(e, "USE_PROFILES") && e.USE_PROFILES, Le = !1 !== e.ALLOW_ARIA_ATTR, ke = !1 !== e.ALLOW_DATA_ATTR, Ie = e.ALLOW_UNKNOWN_PROTOCOLS || !1, Me = !1 !== e.ALLOW_SELF_CLOSE_IN_ATTR, Ue = e.SAFE_FOR_TEMPLATES || !1, ze = !1 !== e.SAFE_FOR_XML, Pe = e.WHOLE_DOCUMENT || !1, Be = e.RETURN_DOM || !1, Ge = e.RETURN_DOM_FRAGMENT || !1, We = e.RETURN_TRUSTED_TYPE || !1, He = e.FORCE_BODY || !1, Ye = !1 !== e.SANITIZE_DOM, je = e.SANITIZE_NAMED_PROPS || !1, Xe = !1 !== e.KEEP_CONTENT, qe = e.IN_PLACE || !1, Se = e.ALLOWED_URI_REGEXP || X, rt = e.NAMESPACE || ot, ct = e.MATHML_TEXT_INTEGRATION_POINTS || ct, st = e.HTML_INTEGRATION_POINTS || st, Ce = e.CUSTOM_ELEMENT_HANDLING || {}, e.CUSTOM_ELEMENT_HANDLING && gt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck) && (Ce.tagNameCheck = e.CUSTOM_ELEMENT_HANDLING.tagNameCheck), e.CUSTOM_ELEMENT_HANDLING && gt(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck) && (Ce.attributeNameCheck = e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck), e.CUSTOM_ELEMENT_HANDLING && "boolean" == typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (Ce.allowCustomizedBuiltInElements = e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements), Ue && (ke = !1), Ge && (Be = !0), $e && (Ne = R({}, U), Re = [], !0 === $e.html && (R(Ne, O), R(Re, z)), !0 === $e.svg && (R(Ne, x), R(Re, P), R(Re, H)), !0 === $e.svgFilters && (R(Ne, L), R(Re, P), R(Re, H)), !0 === $e.mathMl && (R(Ne, I), R(Re, F), R(Re, H))), e.ADD_TAGS && ("function" == typeof e.ADD_TAGS ? xe.tagCheck = e.ADD_TAGS : (Ne === we && (Ne = C(Ne)), R(Ne, e.ADD_TAGS, ft))), e.ADD_ATTR && ("function" == typeof e.ADD_ATTR ? xe.attributeCheck = e.ADD_ATTR : (Re === De && (Re = C(Re)), R(Re, e.ADD_ATTR, ft))), e.ADD_URI_SAFE_ATTR && R(Qe, e.ADD_URI_SAFE_ATTR, ft), e.FORBID_CONTENTS && (Ke === Ve && (Ke = C(Ke)), R(Ke, e.FORBID_CONTENTS, ft)), Xe && (Ne["#text"] = !0), Pe && R(Ne, ["html", "head", "body"]), Ne.table && (R(Ne, ["tbody"]), delete ve.tbody), e.TRUSTED_TYPES_POLICY) { if ("function" != typeof e.TRUSTED_TYPES_POLICY.createHTML) throw S('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.'); if ("function" != typeof e.TRUSTED_TYPES_POLICY.createScriptURL) throw S('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.'); le = e.TRUSTED_TYPES_POLICY, ce = le.createHTML("") } else void 0 === le && (le = function (e, t) { if ("object" != typeof e || "function" != typeof e.createPolicy) return null; let n = null; const o = "data-tt-policy-suffix"; t && t.hasAttribute(o) && (n = t.getAttribute(o)); const r = "dompurify" + (n ? "#" + n : ""); try { return e.createPolicy(r, { createHTML: e => e, createScriptURL: e => e }) } catch (e) { return console.warn("TrustedTypes policy " + r + " could not be created."), null } }(j, c)), null !== le && "string" == typeof ce && (ce = le.createHTML("")); i && i(e), dt = e } }, yt = R({}, [...x, ...L, ...k]), Et = R({}, [...I, ...M]), At = function (e) { f(o.removed, { element: e }); try { ae(e).removeChild(e) } catch (t) { V(e) } }, _t = function (e, t) { try { f(o.removed, { attribute: t.getAttributeNode(e), from: t }) } catch (e) { f(o.removed, { attribute: null, from: t }) } if (t.removeAttribute(e), "is" === e) if (Be || Ge) try { At(t) } catch (e) {} else try { t.setAttribute(e, "") } catch (e) {} }, bt = function (e) { let t = null, n = null; if (He) e = "" + e; else { const t = T(e, /^[\r\n\t ]+/); n = t && t[0] } "application/xhtml+xml" === mt && rt === ot && (e = '' + e + ""); const o = le ? le.createHTML(e) : e; if (rt === ot) try { t = (new Y).parseFromString(o, mt) } catch (e) {} if (!t || !t.documentElement) { t = se.createDocument(rt, "template", null); try { t.documentElement.innerHTML = it ? ce : o } catch (e) {} } const i = t.body || t.documentElement; return e && n && i.insertBefore(r.createTextNode(n), i.childNodes[0] || null), rt === ot ? pe.call(t, Pe ? "html" : "body")[0] : Pe ? t.documentElement : i }, St = function (e) { return ue.call(e.ownerDocument || e, e, B.SHOW_ELEMENT | B.SHOW_COMMENT | B.SHOW_TEXT | B.SHOW_PROCESSING_INSTRUCTION | B.SHOW_CDATA_SECTION, null) }, Nt = function (e) { return e instanceof W && ("string" != typeof e.nodeName || "string" != typeof e.textContent || "function" != typeof e.removeChild || !(e.attributes instanceof G) || "function" != typeof e.removeAttribute || "function" != typeof e.setAttribute || "string" != typeof e.namespaceURI || "function" != typeof e.insertBefore || "function" != typeof e.hasChildNodes) }, wt = function (e) { return "function" == typeof w && e instanceof w }; function Rt(e, t, n) { u(e, (e => { e.call(o, t, n, dt) })) } const Dt = function (e) { let t = null; if (Rt(de.beforeSanitizeElements, e, null), Nt(e)) return At(e), !0; const n = ft(e.nodeName); if (Rt(de.uponSanitizeElement, e, { tagName: n, allowedTags: Ne }), ze && e.hasChildNodes() && !wt(e.firstElementChild) && b(/<[/\w!]/g, e.innerHTML) && b(/<[/\w!]/g, e.textContent)) return At(e), !0; if (e.nodeType === ee) return At(e), !0; if (ze && e.nodeType === te && b(/<[/\w]/g, e.data)) return At(e), !0; if (!(xe.tagCheck instanceof Function && xe.tagCheck(n)) && (!Ne[n] || ve[n])) { if (!ve[n] && vt(n)) { if (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, n)) return !1; if (Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(n)) return !1 } if (Xe && !Ke[n]) { const t = ae(e) || e.parentNode, n = ie(e) || e.childNodes; if (n && t) { for (let o = n.length - 1; o >= 0; --o) { const r = $(n[o], !0); r.__removalCount = (e.__removalCount || 0) + 1, t.insertBefore(r, re(e)) } } } return At(e), !0 } return e instanceof D && !function (e) { let t = ae(e); t && t.tagName || (t = { namespaceURI: rt, tagName: "template" }); const n = h(e.tagName), o = h(t.tagName); return !!at[e.namespaceURI] && (e.namespaceURI === nt ? t.namespaceURI === ot ? "svg" === n : t.namespaceURI === tt ? "svg" === n && ("annotation-xml" === o || ct[o]) : Boolean(yt[n]) : e.namespaceURI === tt ? t.namespaceURI === ot ? "math" === n : t.namespaceURI === nt ? "math" === n && st[o] : Boolean(Et[n]) : e.namespaceURI === ot ? !(t.namespaceURI === nt && !st[o]) && !(t.namespaceURI === tt && !ct[o]) && !Et[n] && (ut[n] || !yt[n]) : !("application/xhtml+xml" !== mt || !at[e.namespaceURI])) }(e) ? (At(e), !0) : "noscript" !== n && "noembed" !== n && "noframes" !== n || !b(/<\/no(script|embed|frames)/i, e.innerHTML) ? (Ue && e.nodeType === Q && (t = e.textContent, u([he, ge, Te], (e => { t = y(t, e, " ") })), e.textContent !== t && (f(o.removed, { element: e.cloneNode() }), e.textContent = t)), Rt(de.afterSanitizeElements, e, null), !1) : (At(e), !0) }, Ct = function (e, t, n) { if (Ye && ("id" === t || "name" === t) && (n in r || n in ht)) return !1; if (ke && !Oe[t] && b(ye, t)); else if (Le && b(Ee, t)); else if (xe.attributeCheck instanceof Function && xe.attributeCheck(t, e)); else if (!Re[t] || Oe[t]) { if (!(vt(e) && (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, e) || Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(e)) && (Ce.attributeNameCheck instanceof RegExp && b(Ce.attributeNameCheck, t) || Ce.attributeNameCheck instanceof Function && Ce.attributeNameCheck(t, e)) || "is" === t && Ce.allowCustomizedBuiltInElements && (Ce.tagNameCheck instanceof RegExp && b(Ce.tagNameCheck, n) || Ce.tagNameCheck instanceof Function && Ce.tagNameCheck(n)))) return !1 } else if (Qe[t]); else if (b(Se, y(n, _e, ""))); else if ("src" !== t && "xlink:href" !== t && "href" !== t || "script" === e || 0 !== E(n, "data:") || !Ze[e]) { if (Ie && !b(Ae, y(n, _e, ""))); else if (n) return !1 } else; return !0 }, vt = function (e) { return "annotation-xml" !== e && T(e, be) }, Ot = function (e) { Rt(de.beforeSanitizeAttributes, e, null); const { attributes: t } = e; if (!t || Nt(e)) return; const n = { attrName: "", attrValue: "", keepAttr: !0, allowedAttributes: Re, forceKeepAttr: void 0 }; let r = t.length; for (; r--;) { const i = t[r], { name: a, namespaceURI: l, value: c } = i, s = ft(a), m = c; let f = "value" === a ? m : A(m); if (n.attrName = s, n.attrValue = f, n.keepAttr = !0, n.forceKeepAttr = void 0, Rt(de.uponSanitizeAttribute, e, n), f = n.attrValue, !je || "id" !== s && "name" !== s || (_t(a, e), f = "user-content-" + f), ze && b(/((--!?|])>)|<\/(style|title|textarea)/i, f)) { _t(a, e); continue } if ("attributename" === s && T(f, "href")) { _t(a, e); continue } if (n.forceKeepAttr) continue; if (!n.keepAttr) { _t(a, e); continue } if (!Me && b(/\/>/i, f)) { _t(a, e); continue } Ue && u([he, ge, Te], (e => { f = y(f, e, " ") })); const d = ft(e.nodeName); if (Ct(d, s, f)) { if (le && "object" == typeof j && "function" == typeof j.getAttributeType) if (l); else switch (j.getAttributeType(d, s)) { case "TrustedHTML": f = le.createHTML(f); break; case "TrustedScriptURL": f = le.createScriptURL(f) }if (f !== m) try { l ? e.setAttributeNS(l, a, f) : e.setAttribute(a, f), Nt(e) ? At(e) : p(o.removed) } catch (t) { _t(a, e) } } else _t(a, e) } Rt(de.afterSanitizeAttributes, e, null) }, xt = function e(t) { let n = null; const o = St(t); for (Rt(de.beforeSanitizeShadowDOM, t, null); n = o.nextNode();)Rt(de.uponSanitizeShadowNode, n, null), Dt(n), Ot(n), n.content instanceof s && e(n.content); Rt(de.afterSanitizeShadowDOM, t, null) }; return o.sanitize = function (e) { let t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : {}, n = null, r = null, i = null, l = null; if (it = !e, it && (e = "\x3c!--\x3e"), "string" != typeof e && !wt(e)) { if ("function" != typeof e.toString) throw S("toString is not a function"); if ("string" != typeof (e = e.toString())) throw S("dirty is not a string, aborting") } if (!o.isSupported) return e; if (Fe || Tt(t), o.removed = [], "string" == typeof e && (qe = !1), qe) { if (e.nodeName) { const t = ft(e.nodeName); if (!Ne[t] || ve[t]) throw S("root node is forbidden and cannot be sanitized in-place") } } else if (e instanceof w) n = bt("\x3c!----\x3e"), r = n.ownerDocument.importNode(e, !0), r.nodeType === J && "BODY" === r.nodeName || "HTML" === r.nodeName ? n = r : n.appendChild(r); else { if (!Be && !Ue && !Pe && -1 === e.indexOf("<")) return le && We ? le.createHTML(e) : e; if (n = bt(e), !n) return Be ? null : We ? ce : "" } n && He && At(n.firstChild); const c = St(qe ? e : n); for (; i = c.nextNode();)Dt(i), Ot(i), i.content instanceof s && xt(i.content); if (qe) return e; if (Be) { if (Ge) for (l = me.call(n.ownerDocument); n.firstChild;)l.appendChild(n.firstChild); else l = n; return (Re.shadowroot || Re.shadowrootmode) && (l = fe.call(a, l, !0)), l } let m = Pe ? n.outerHTML : n.innerHTML; return Pe && Ne["!doctype"] && n.ownerDocument && n.ownerDocument.doctype && n.ownerDocument.doctype.name && b(K, n.ownerDocument.doctype.name) && (m = "\n" + m), Ue && u([he, ge, Te], (e => { m = y(m, e, " ") })), le && We ? le.createHTML(m) : m }, o.setConfig = function () { Tt(arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {}), Fe = !0 }, o.clearConfig = function () { dt = null, Fe = !1 }, o.isValidAttribute = function (e, t, n) { dt || Tt({}); const o = ft(e), r = ft(t); return Ct(o, r, n) }, o.addHook = function (e, t) { "function" == typeof t && f(de[e], t) }, o.removeHook = function (e, t) { if (void 0 !== t) { const n = m(de[e], t); return -1 === n ? void 0 : d(de[e], n, 1)[0] } return p(de[e]) }, o.removeHooks = function (e) { de[e] = [] }, o.removeAllHooks = function () { de = { afterSanitizeAttributes: [], afterSanitizeElements: [], afterSanitizeShadowDOM: [], beforeSanitizeAttributes: [], beforeSanitizeElements: [], beforeSanitizeShadowDOM: [], uponSanitizeAttribute: [], uponSanitizeElement: [], uponSanitizeShadowNode: [] } }, o }(); return re }));
-
- // Create a custom CSP policy (using DOM Purify for added security)
- let goodTube_csp = false;
- if (window.trustedTypes && window.trustedTypes.createPolicy && DOMPurify) {
- goodTube_csp = window.trustedTypes.createPolicy("GoodTubePolicy", {
- createHTML: (input) => DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false })
- });
- }
-
-
- /* Helper functions
- ------------------------------------------------------------------------------------------ */
- // Setup GET parameters
- function goodTube_helper_setupGetParams() {
- let getParams = {};
-
- document.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g, function () {
- function decode(s) {
- return decodeURIComponent(s.split("+").join(" "));
- }
-
- getParams[decode(arguments[1])] = decode(arguments[2]);
- });
-
- // For some users, the URL will contain the video ID as part of the URL
- // Example - "/watch/xxxx" or "/live/xxxx"
- // In this case, we want to add it manually as "v" (just like for /watch?v=xxxx)
- if (goodTube_helper_watchingVideo() && typeof getParams['v'] === 'undefined') {
- let splitString = '';
- if (window.location.href.indexOf('/watch/') !== -1) {
- splitString = '/watch/';
- }
- else {
- splitString = '/live/';
- }
-
- let bits = window.location.href.split(splitString);
- if (bits.length === 2) {
- let endBits = bits[1].split('?');
- getParams['v'] = endBits[endBits.length - 1];
- }
- }
-
- return getParams;
- }
-
- // Set a cookie
- function goodTube_helper_setCookie(name, value, days = 399) {
- // Force new cookie names, we had the path attribute wrong...sorry all this will reset your settings (22/10/2025)
- name = name + '_new';
-
- document.cookie = name + "=" + encodeURIComponent(value) + ";SameSite=Lax;path=/;max-age=" + (days * 24 * 60 * 60);
- }
-
- // Get a cookie
- function goodTube_helper_getCookie(name) {
- // Force new cookie names, we had the path attribute wrong...sorry all this will reset your settings (22/10/2025)
- name = name + '_new';
-
- // Split the cookie string and get all individual name=value pairs in an array
- let cookies = document.cookie.split(";");
-
- // Loop through the array elements
- for (let i = 0; i < cookies.length; i++) {
- let cookie = cookies[i].split("=");
-
- // Removing whitespace at the beginning of the cookie name and compare it with the given string
- if (name == cookie[0].trim()) {
- // Decode the cookie value and return
- return decodeURIComponent(cookie[1]);
- }
- }
-
- // Return null if not found
- return null;
- }
-
- // Simulate a click (without changing focus)
- function goodTube_helper_click(element) {
- if (element) {
- element.dispatchEvent(new PointerEvent('mousedown', { bubbles: true, cancelable: true, button: 0 }));
- element.dispatchEvent(new PointerEvent('click', { bubbles: true, cancelable: true, button: 0 }));
- element.dispatchEvent(new PointerEvent('mouseup', { bubbles: true, cancelable: true, button: 0 }));
- }
- }
-
- // Add a CSS class to show or hide elements
- function goodTube_helper_showHide_init() {
- let style = document.createElement('style');
- style.textContent = `
+(()=>{var un=Object.defineProperty;var a=(wt,f)=>un(wt,"name",{value:f,configurable:!0});(function(){"use strict";var wt=Object.defineProperty,f=a((e,t)=>wt(e,"name",{value:t,configurable:!0}),"c"),{entries:mo,setPrototypeOf:go,isFrozen:ji,getPrototypeOf:qi,getOwnPropertyDescriptor:Wi}=Object,{freeze:R,seal:V,create:At}=Object,{apply:Et,construct:St}=typeof Reflect<"u"&&Reflect;R||(R=f(function(e){return e},"freeze")),V||(V=f(function(e){return e},"seal")),Et||(Et=f(function(e,t){for(var i=arguments.length,n=new Array(i>2?i-2:0),s=2;s1?t-1:0),n=1;n1?i-1:0),s=1;s2&&arguments[2]!==void 0?arguments[2]:je;go&&go(e,null);let n=t.length;for(;n--;){let s=t[n];if(typeof s=="string"){let c=i(s);c!==s&&(ji(t)||(t[n]=c),s=c)}e[s]=!0}return e}a(p,"s"),f(p,"addToSet");function _o(e){for(let t=0;t/gm),ia=V(/\$\{[\w\W]*/gm),aa=V(/^data-[\-\w.\u00B7-\uFFFF]+$/),na=V(/^aria-[\-\w]+$/),ko=V(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ra=V(/^(?:\w+script|data):/i),da=V(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),wo=V(/^html$/i),la=V(/^[a-z][.\w]*(-[.\w]+)+$/i),Ao=Object.freeze({__proto__:null,ARIA_ATTR:na,ATTR_WHITESPACE:da,CUSTOM_ELEMENT:la,DATA_ATTR:aa,DOCTYPE_NAME:wo,ERB_EXPR:oa,IS_ALLOWED_URI:ko,IS_SCRIPT_OR_DATA:ra,MUSTACHE_EXPR:ta,TMPLIT_EXPR:ia}),Ne={element:1,attribute:2,text:3,cdataSection:4,entityReference:5,entityNode:6,progressingInstruction:7,comment:8,document:9,documentType:10,documentFragment:11,notation:12},sa=f(function(){return typeof window>"u"?null:window},"getGlobal"),ua=f(function(e,t){if(typeof e!="object"||typeof e.createPolicy!="function")return null;let i=null,n="data-tt-policy-suffix";t&&t.hasAttribute(n)&&(i=t.getAttribute(n));let s="dompurify"+(i?"#"+i:"");try{return e.createPolicy(s,{createHTML(c){return c},createScriptURL(c){return c}})}catch{return console.warn("TrustedTypes policy "+s+" could not be created."),null}},"_createTrustedTypesPolicy"),Eo=f(function(){return{afterSanitizeAttributes:[],afterSanitizeElements:[],afterSanitizeShadowDOM:[],beforeSanitizeAttributes:[],beforeSanitizeElements:[],beforeSanitizeShadowDOM:[],uponSanitizeAttribute:[],uponSanitizeElement:[],uponSanitizeShadowNode:[]}},"_createHooksMap");function Nt(){let e=arguments.length>0&&arguments[0]!==void 0?arguments[0]:sa(),t=f(o=>Nt(o),"DOMPurify");if(t.version="3.3.0",t.removed=[],!e||!e.document||e.document.nodeType!==Ne.document||!e.Element)return t.isSupported=!1,t;let{document:i}=e,n=i,s=n.currentScript,{DocumentFragment:c,HTMLTemplateElement:g,Node:w,Element:x,NodeFilter:A,NamedNodeMap:oe=e.NamedNodeMap||e.MozNamedAttrMap,HTMLFormElement:Fe,DOMParser:z,trustedTypes:ce}=e,ie=x.prototype,pt=ge(ie,"cloneNode"),Ue=ge(ie,"remove"),ft=ge(ie,"nextSibling"),H=ge(ie,"childNodes"),Y=ge(ie,"parentNode");if(typeof g=="function"){let o=i.createElement("template");o.content&&o.content.ownerDocument&&(i=o.content.ownerDocument)}let h,B="",{implementation:ke,createNodeIterator:mt,createDocumentFragment:gt,getElementsByTagName:bt}=i,{importNode:Za}=n,O=Eo();t.isSupported=typeof mo=="function"&&typeof Y=="function"&&ke&&ke.createHTMLDocument!==void 0;let{MUSTACHE_EXPR:$t,ERB_EXPR:eo,TMPLIT_EXPR:to,DATA_ATTR:Ka,ARIA_ATTR:Ja,IS_SCRIPT_OR_DATA:Xa,ATTR_WHITESPACE:vi,CUSTOM_ELEMENT:$a}=Ao,{IS_ALLOWED_URI:ki}=Ao,E=null,wi=p({},[...ho,...Dt,...Mt,...Ct,...To]),L=null,Ai=p({},[...xo,...Rt,...vo,...qe]),v=Object.seal(At(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),He=null,oo=null,we=Object.seal(At(null,{tagCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeCheck:{writable:!0,configurable:!1,enumerable:!0,value:null}})),Ei=!0,io=!0,Si=!1,Ii=!0,Ae=!1,yt=!0,pe=!1,ao=!1,no=!1,Ee=!1,_t=!1,ht=!1,Li=!0,Di=!1,en="user-content-",ro=!0,Ye=!1,Se={},Ie=null,Mi=p({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Ci=null,Ri=p({},["audio","video","img","source","image","track"]),lo=null,Ni=p({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Tt="http://www.w3.org/1998/Math/MathML",xt="http://www.w3.org/2000/svg",$="http://www.w3.org/1999/xhtml",Le=$,so=!1,uo=null,tn=p({},[Tt,xt,$],It),vt=p({},["mi","mo","mn","ms","mtext"]),kt=p({},["annotation-xml"]),on=p({},["title","style","font","a","script"]),Ge=null,an=["application/xhtml+xml","text/html"],nn="text/html",S=null,De=null,rn=i.createElement("form"),Oi=f(function(o){return o instanceof RegExp||o instanceof Function},"isRegexOrFunction"),co=f(function(){let o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};if(!(De&&De===o)){if((!o||typeof o!="object")&&(o={}),o=q(o),Ge=an.indexOf(o.PARSER_MEDIA_TYPE)===-1?nn:o.PARSER_MEDIA_TYPE,S=Ge==="application/xhtml+xml"?It:je,E=G(o,"ALLOWED_TAGS")?p({},o.ALLOWED_TAGS,S):wi,L=G(o,"ALLOWED_ATTR")?p({},o.ALLOWED_ATTR,S):Ai,uo=G(o,"ALLOWED_NAMESPACES")?p({},o.ALLOWED_NAMESPACES,It):tn,lo=G(o,"ADD_URI_SAFE_ATTR")?p(q(Ni),o.ADD_URI_SAFE_ATTR,S):Ni,Ci=G(o,"ADD_DATA_URI_TAGS")?p(q(Ri),o.ADD_DATA_URI_TAGS,S):Ri,Ie=G(o,"FORBID_CONTENTS")?p({},o.FORBID_CONTENTS,S):Mi,He=G(o,"FORBID_TAGS")?p({},o.FORBID_TAGS,S):q({}),oo=G(o,"FORBID_ATTR")?p({},o.FORBID_ATTR,S):q({}),Se=G(o,"USE_PROFILES")?o.USE_PROFILES:!1,Ei=o.ALLOW_ARIA_ATTR!==!1,io=o.ALLOW_DATA_ATTR!==!1,Si=o.ALLOW_UNKNOWN_PROTOCOLS||!1,Ii=o.ALLOW_SELF_CLOSE_IN_ATTR!==!1,Ae=o.SAFE_FOR_TEMPLATES||!1,yt=o.SAFE_FOR_XML!==!1,pe=o.WHOLE_DOCUMENT||!1,Ee=o.RETURN_DOM||!1,_t=o.RETURN_DOM_FRAGMENT||!1,ht=o.RETURN_TRUSTED_TYPE||!1,no=o.FORCE_BODY||!1,Li=o.SANITIZE_DOM!==!1,Di=o.SANITIZE_NAMED_PROPS||!1,ro=o.KEEP_CONTENT!==!1,Ye=o.IN_PLACE||!1,ki=o.ALLOWED_URI_REGEXP||ko,Le=o.NAMESPACE||$,vt=o.MATHML_TEXT_INTEGRATION_POINTS||vt,kt=o.HTML_INTEGRATION_POINTS||kt,v=o.CUSTOM_ELEMENT_HANDLING||{},o.CUSTOM_ELEMENT_HANDLING&&Oi(o.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(v.tagNameCheck=o.CUSTOM_ELEMENT_HANDLING.tagNameCheck),o.CUSTOM_ELEMENT_HANDLING&&Oi(o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(v.attributeNameCheck=o.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),o.CUSTOM_ELEMENT_HANDLING&&typeof o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(v.allowCustomizedBuiltInElements=o.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Ae&&(io=!1),_t&&(Ee=!0),Se&&(E=p({},To),L=[],Se.html===!0&&(p(E,ho),p(L,xo)),Se.svg===!0&&(p(E,Dt),p(L,Rt),p(L,qe)),Se.svgFilters===!0&&(p(E,Mt),p(L,Rt),p(L,qe)),Se.mathMl===!0&&(p(E,Ct),p(L,vo),p(L,qe))),o.ADD_TAGS&&(typeof o.ADD_TAGS=="function"?we.tagCheck=o.ADD_TAGS:(E===wi&&(E=q(E)),p(E,o.ADD_TAGS,S))),o.ADD_ATTR&&(typeof o.ADD_ATTR=="function"?we.attributeCheck=o.ADD_ATTR:(L===Ai&&(L=q(L)),p(L,o.ADD_ATTR,S))),o.ADD_URI_SAFE_ATTR&&p(lo,o.ADD_URI_SAFE_ATTR,S),o.FORBID_CONTENTS&&(Ie===Mi&&(Ie=q(Ie)),p(Ie,o.FORBID_CONTENTS,S)),ro&&(E["#text"]=!0),pe&&p(E,["html","head","body"]),E.table&&(p(E,["tbody"]),delete He.tbody),o.TRUSTED_TYPES_POLICY){if(typeof o.TRUSTED_TYPES_POLICY.createHTML!="function")throw Re('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if(typeof o.TRUSTED_TYPES_POLICY.createScriptURL!="function")throw Re('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');h=o.TRUSTED_TYPES_POLICY,B=h.createHTML("")}else h===void 0&&(h=ua(ce,s)),h!==null&&typeof B=="string"&&(B=h.createHTML(""));R&&R(o),De=o}},"_parseConfig"),Bi=p({},[...Dt,...Mt,...$i]),Pi=p({},[...Ct,...ea]),dn=f(function(o){let d=Y(o);(!d||!d.tagName)&&(d={namespaceURI:Le,tagName:"template"});let r=je(o.tagName),y=je(d.tagName);return uo[o.namespaceURI]?o.namespaceURI===xt?d.namespaceURI===$?r==="svg":d.namespaceURI===Tt?r==="svg"&&(y==="annotation-xml"||vt[y]):!!Bi[r]:o.namespaceURI===Tt?d.namespaceURI===$?r==="math":d.namespaceURI===xt?r==="math"&&kt[y]:!!Pi[r]:o.namespaceURI===$?d.namespaceURI===xt&&!kt[y]||d.namespaceURI===Tt&&!vt[y]?!1:!Pi[r]&&(on[r]||!Bi[r]):!!(Ge==="application/xhtml+xml"&&uo[o.namespaceURI]):!1},"_checkValidNamespace"),fe=f(function(o){Me(t.removed,{element:o});try{Y(o).removeChild(o)}catch{Ue(o)}},"_forceRemove"),me=f(function(o,d){try{Me(t.removed,{attribute:d.getAttributeNode(o),from:d})}catch{Me(t.removed,{attribute:null,from:d})}if(d.removeAttribute(o),o==="is")if(Ee||_t)try{fe(d)}catch{}else try{d.setAttribute(o,"")}catch{}},"_removeAttribute"),zi=f(function(o){let d=null,r=null;if(no)o=""+o;else{let T=Lt(o,/^[\r\n\t ]+/);r=T&&T[0]}Ge==="application/xhtml+xml"&&Le===$&&(o=''+o+"");let y=h?h.createHTML(o):o;if(Le===$)try{d=new z().parseFromString(y,Ge)}catch{}if(!d||!d.documentElement){d=ke.createDocument(Le,"template",null);try{d.documentElement.innerHTML=so?B:y}catch{}}let D=d.body||d.documentElement;return o&&r&&D.insertBefore(i.createTextNode(r),D.childNodes[0]||null),Le===$?bt.call(d,pe?"html":"body")[0]:pe?d.documentElement:D},"_initDocument"),Vi=f(function(o){return mt.call(o.ownerDocument||o,o,A.SHOW_ELEMENT|A.SHOW_COMMENT|A.SHOW_TEXT|A.SHOW_PROCESSING_INSTRUCTION|A.SHOW_CDATA_SECTION,null)},"_createNodeIterator"),po=f(function(o){return o instanceof Fe&&(typeof o.nodeName!="string"||typeof o.textContent!="string"||typeof o.removeChild!="function"||!(o.attributes instanceof oe)||typeof o.removeAttribute!="function"||typeof o.setAttribute!="function"||typeof o.namespaceURI!="string"||typeof o.insertBefore!="function"||typeof o.hasChildNodes!="function")},"_isClobbered"),Fi=f(function(o){return typeof w=="function"&&o instanceof w},"_isNode");function J(o,d,r){Qe(o,y=>{y.call(t,d,r,De)})}a(J,"C"),f(J,"_executeHooks");let Ui=f(function(o){let d=null;if(J(O.beforeSanitizeElements,o,null),po(o))return fe(o),!0;let r=S(o.nodeName);if(J(O.uponSanitizeElement,o,{tagName:r,allowedTags:E}),yt&&o.hasChildNodes()&&!Fi(o.firstElementChild)&&N(/<[/\w!]/g,o.innerHTML)&&N(/<[/\w!]/g,o.textContent)||o.nodeType===Ne.progressingInstruction||yt&&o.nodeType===Ne.comment&&N(/<[/\w]/g,o.data))return fe(o),!0;if(!(we.tagCheck instanceof Function&&we.tagCheck(r))&&(!E[r]||He[r])){if(!He[r]&&Yi(r)&&(v.tagNameCheck instanceof RegExp&&N(v.tagNameCheck,r)||v.tagNameCheck instanceof Function&&v.tagNameCheck(r)))return!1;if(ro&&!Ie[r]){let y=Y(o)||o.parentNode,D=H(o)||o.childNodes;if(D&&y){let T=D.length;for(let ee=T-1;ee>=0;--ee){let Q=pt(D[ee],!0);Q.__removalCount=(o.__removalCount||0)+1,y.insertBefore(Q,ft(o))}}}return fe(o),!0}return o instanceof x&&!dn(o)||(r==="noscript"||r==="noembed"||r==="noframes")&&N(/<\/no(script|embed|frames)/i,o.innerHTML)?(fe(o),!0):(Ae&&o.nodeType===Ne.text&&(d=o.textContent,Qe([$t,eo,to],y=>{d=Ce(d,y," ")}),o.textContent!==d&&(Me(t.removed,{element:o.cloneNode()}),o.textContent=d)),J(O.afterSanitizeElements,o,null),!1)},"_sanitizeElements"),Hi=f(function(o,d,r){if(Li&&(d==="id"||d==="name")&&(r in i||r in rn))return!1;if(!(io&&!oo[d]&&N(Ka,d))&&!(Ei&&N(Ja,d))&&!(we.attributeCheck instanceof Function&&we.attributeCheck(d,o))){if(!L[d]||oo[d]){if(!(Yi(o)&&(v.tagNameCheck instanceof RegExp&&N(v.tagNameCheck,o)||v.tagNameCheck instanceof Function&&v.tagNameCheck(o))&&(v.attributeNameCheck instanceof RegExp&&N(v.attributeNameCheck,d)||v.attributeNameCheck instanceof Function&&v.attributeNameCheck(d,o))||d==="is"&&v.allowCustomizedBuiltInElements&&(v.tagNameCheck instanceof RegExp&&N(v.tagNameCheck,r)||v.tagNameCheck instanceof Function&&v.tagNameCheck(r))))return!1}else if(!lo[d]&&!N(ki,Ce(r,vi,""))&&!((d==="src"||d==="xlink:href"||d==="href")&&o!=="script"&&Ji(r,"data:")===0&&Ci[o])&&!(Si&&!N(Xa,Ce(r,vi,"")))&&r)return!1}return!0},"_isValidAttribute"),Yi=f(function(o){return o!=="annotation-xml"&&Lt(o,$a)},"_isBasicCustomElement"),Gi=f(function(o){J(O.beforeSanitizeAttributes,o,null);let{attributes:d}=o;if(!d||po(o))return;let r={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:L,forceKeepAttr:void 0},y=d.length;for(;y--;){let D=d[y],{name:T,namespaceURI:ee,value:Q}=D,j=S(T),fo=Q,M=T==="value"?fo:Xi(fo);if(r.attrName=j,r.attrValue=M,r.keepAttr=!0,r.forceKeepAttr=void 0,J(O.uponSanitizeAttribute,o,r),M=r.attrValue,Di&&(j==="id"||j==="name")&&(me(T,o),M=en+M),yt&&N(/((--!?|])>)|<\/(style|title|textarea)/i,M)){me(T,o);continue}if(j==="attributename"&&Lt(M,"href")){me(T,o);continue}if(r.forceKeepAttr)continue;if(!r.keepAttr){me(T,o);continue}if(!Ii&&N(/\/>/i,M)){me(T,o);continue}Ae&&Qe([$t,eo,to],sn=>{M=Ce(M,sn," ")});let Qi=S(o.nodeName);if(!Hi(Qi,j,M)){me(T,o);continue}if(h&&typeof ce=="object"&&typeof ce.getAttributeType=="function"&&!ee)switch(ce.getAttributeType(Qi,j)){case"TrustedHTML":{M=h.createHTML(M);break}case"TrustedScriptURL":{M=h.createScriptURL(M);break}}if(M!==fo)try{ee?o.setAttributeNS(ee,T,M):o.setAttribute(T,M),po(o)?fe(o):bo(t.removed)}catch{me(T,o)}}J(O.afterSanitizeAttributes,o,null)},"_sanitizeAttributes"),ln=f(a(function o(d){let r=null,y=Vi(d);for(J(O.beforeSanitizeShadowDOM,d,null);r=y.nextNode();)J(O.uponSanitizeShadowNode,r,null),Ui(r),Gi(r),r.content instanceof c&&o(r.content);J(O.afterSanitizeShadowDOM,d,null)},"i"),"_sanitizeShadowDOM");return t.sanitize=function(o){let d=arguments.length>1&&arguments[1]!==void 0?arguments[1]:{},r=null,y=null,D=null,T=null;if(so=!o,so&&(o=""),typeof o!="string"&&!Fi(o))if(typeof o.toString=="function"){if(o=o.toString(),typeof o!="string")throw Re("dirty is not a string, aborting")}else throw Re("toString is not a function");if(!t.isSupported)return o;if(ao||co(d),t.removed=[],typeof o=="string"&&(Ye=!1),Ye){if(o.nodeName){let j=S(o.nodeName);if(!E[j]||He[j])throw Re("root node is forbidden and cannot be sanitized in-place")}}else if(o instanceof w)r=zi(""),y=r.ownerDocument.importNode(o,!0),y.nodeType===Ne.element&&y.nodeName==="BODY"||y.nodeName==="HTML"?r=y:r.appendChild(y);else{if(!Ee&&!Ae&&!pe&&o.indexOf("<")===-1)return h&&ht?h.createHTML(o):o;if(r=zi(o),!r)return Ee?null:ht?B:""}r&&no&&fe(r.firstChild);let ee=Vi(Ye?o:r);for(;D=ee.nextNode();)Ui(D),Gi(D),D.content instanceof c&&ln(D.content);if(Ye)return o;if(Ee){if(_t)for(T=gt.call(r.ownerDocument);r.firstChild;)T.appendChild(r.firstChild);else T=r;return(L.shadowroot||L.shadowrootmode)&&(T=Za.call(n,T,!0)),T}let Q=pe?r.outerHTML:r.innerHTML;return pe&&E["!doctype"]&&r.ownerDocument&&r.ownerDocument.doctype&&r.ownerDocument.doctype.name&&N(wo,r.ownerDocument.doctype.name)&&(Q=""+Q),Ae&&Qe([$t,eo,to],j=>{Q=Ce(Q,j," ")}),h&&ht?h.createHTML(Q):Q},t.setConfig=function(){let o=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};co(o),ao=!0},t.clearConfig=function(){De=null,ao=!1},t.isValidAttribute=function(o,d,r){De||co({});let y=S(o),D=S(d);return Hi(y,D,r)},t.addHook=function(o,d){typeof d=="function"&&Me(O[o],d)},t.removeHook=function(o,d){if(d!==void 0){let r=Zi(O[o],d);return r===-1?void 0:Ki(O[o],r,1)[0]}return bo(O[o])},t.removeHooks=function(o){O[o]=[]},t.removeAllHooks=function(){O=Eo()},t}a(Nt,"St"),f(Nt,"createDOMPurify");var So=Nt();let Oe=!1;window.trustedTypes&&window.trustedTypes.createPolicy&&So&&(Oe=window.trustedTypes.createPolicy("GoodTubePolicy",{createHTML:a(e=>So.sanitize(e,{RETURN_TRUSTED_TYPE:!1}),"createHTML")}));function Io(){let e={};if(document.location.search.replace(/\??(?:([^=]+)=([^&]*)&?)/g,function(){function t(i){return decodeURIComponent(i.split("+").join(" "))}a(t,"decode"),e[t(arguments[1])]=t(arguments[2])}),U()&&typeof e.v>"u"){let t="";window.location.href.indexOf("/watch/")!==-1?t="/watch/":t="/live/";let i=window.location.href.split(t);if(i.length===2){let n=i[1].split("?");e.v=n[n.length-1]}}return e}a(Io,"goodTube_helper_setupGetParams");function m(e,t,i=399){e=e+"_new",document.cookie=e+"="+encodeURIComponent(t)+";SameSite=Lax;path=/;max-age="+i*24*60*60}a(m,"goodTube_helper_setCookie");function F(e){e=e+"_new";let t=document.cookie.split(";");for(let i=0;i .pivot-shorts),
ytd-rich-section-renderer,
grid-shelf-view-model {
display: none !important;
}
- `;
-
- // Debug message
- console.log('[GoodTube] Shorts removed');
- }
-
-
- // Hide suggested videos if they're not enabled
- if (goodTube_hideSuggestedVideos === 'true') {
- cssOutput += `
+ `,console.log("[GoodTube] Shorts removed")),Je==="true"&&(e+=`
/* Hide suggested videos */
ytd-watch-flexy #secondary #related {
display: none !important;
@@ -413,291 +96,21 @@
ytd-watch-flexy #secondary:not(:has(ytd-playlist-panel-video-renderer)) {
display: none !important;
}
- `;
-
- // Debug message
- console.log('[GoodTube] Suggested videos removed');
- }
-
- // Hide comments if they're not enabled
- if (goodTube_hideComments === 'true') {
- cssOutput += `
+ `,console.log("[GoodTube] Suggested videos removed")),Xe==="true"&&(e+=`
ytd-item-section-renderer.ytd-comments,
#comments-button,
#shorts-panel-container ytd-engagement-panel-section-list-renderer {
display: none !important;
}
- `;
-
- // Debug message
- console.log('[GoodTube] Comments removed');
- }
-
- // Hide AI summaries if they're not enabled
- if (goodTube_hideAiSummaries === 'true') {
- cssOutput += `
+ `,console.log("[GoodTube] Comments removed")),$e==="true"&&(e+=`
ytd-expandable-metadata-renderer[has-video-summary] {
display: none !important;
}
- `;
-
- // Debug message
- console.log('[GoodTube] AI summaries removed');
- }
-
- // Add the styles to the page
- let style = document.createElement('style');
- style.textContent = cssOutput;
- document.head.appendChild(style);
- }
-
- // Hide shorts (real time)
- function goodTube_youtube_hideShortsRealTime() {
- // If shorts are enabled, don't do anything
- if (goodTube_shorts === 'true') {
- return;
- }
-
- // Redirect from any short to the homepage
- if (window.location.href.indexOf('/shorts') !== -1 && !goodTube_redirectHappened) {
- window.location.href = 'https://youtube.com';
- goodTube_redirectHappened = true;
- }
-
- // Hide shorts links (we can't mark these as "checked" to save on resources, as the URLs seem to change over time)
- let shortsLinks = document.querySelectorAll('a:not(.goodTube_hidden)');
- shortsLinks.forEach((element) => {
- if (element.href.indexOf('shorts/') !== -1) {
- goodTube_helper_hideElement(element);
- goodTube_helper_hideElement(element.closest('ytd-video-renderer'));
- goodTube_helper_hideElement(element.closest('ytd-compact-video-renderer'));
- goodTube_helper_hideElement(element.closest('ytd-rich-grid-media'));
- }
- });
-
- // Hide shorts buttons
- let shortsButtons = document.querySelectorAll('yt-chip-cloud-chip-renderer:not(.goodTube_hidden):not(.goodTube_checked), yt-tab-shape:not(.goodTube_hidden):not(.goodTube_checked), ytd-guide-entry-renderer:not(.goodTube_checked)');
- shortsButtons.forEach((element) => {
- if (element.innerHTML.toLowerCase().indexOf('shorts') !== -1) {
- goodTube_helper_hideElement(element);
- }
-
- // Mark this element as checked to save on resources
- element.classList.add('goodTube_checked');
- });
- }
-
- // Support timestamp links in comments
- function goodTube_youtube_timestampLinks() {
- // Links in video description and comments
- let timestampLinks = document.querySelectorAll('#description a, ytd-comments .yt-core-attributed-string a, ytm-expandable-video-description-body-renderer a, .comment-content a');
-
- // For each link
- timestampLinks.forEach((element) => {
- // Make sure we've not touched it yet, this stops doubling up on event listeners
- if (!element.classList.contains('goodTube_timestampLink') && element.getAttribute('href') && element.getAttribute('href').indexOf(goodTube_getParams['v']) !== -1) {
- element.classList.add('goodTube_timestampLink');
-
- // Add the event listener to send our player to the correct time
- element.addEventListener('click', function () {
- // Define the time to skip to
- let time = 0;
-
- // Get the time from the link (if it exstis)
- let bits = element.getAttribute('href').split('t=');
- if (typeof bits[1] !== 'undefined') {
- time = parseFloat(bits[1].replace('s', ''));
- }
-
- // Skip to the time
- goodTube_player_skipTo(time);
- });
- }
- });
- }
-
- // Mute and pause all Youtube videos
- let goodTube_youtube_pauseMuteVideos_timeout = setTimeout(() => {}, 0);
- function goodTube_youtube_pauseMuteVideos() {
- // IF if shorts are enabled and we're viewing a short
- // OR we're not viewing a video
- if (
- (goodTube_shorts === 'true' && window.location.href.indexOf('/shorts') !== -1)
- ||
- !goodTube_helper_watchingVideo()
- ) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_youtube_pauseMuteVideos_timeout);
-
- // Loop this function
- goodTube_youtube_pauseMuteVideos_timeout = setTimeout(goodTube_youtube_pauseMuteVideos, 100);
-
- // Don't pause or mute videos
- return;
- }
-
- // Pause and mute all HTML videos on the page
- let youtubeVideos = document.querySelectorAll('video');
- youtubeVideos.forEach((video) => {
- // If the "hide the mute" ads fallback is active
- if (goodTube_fallback) {
- // If the video is playing and it's NOT the main player
- if (!video.paused && !video.closest('#movie_player')) {
- // Pause and mute the video
- video.muted = true;
- video.volume = 0;
- video.pause();
- }
- }
- // Otherwise, the "hide and mute" ads fallback is inactive
- else {
- // IF (the video is playing)
- // AND (we're not syncing the main player OR it's not the main player)
- if (!video.paused && (!goodTube_syncingPlayer || !video.closest('#movie_player'))) {
- // Mute the video
- video.muted = true;
- video.volume = 0;
-
- // If ads are not showing OR it's not the main player
- if (!goodTube_helper_adsShowing() || !video.closest('#movie_player')) {
- // Pause the video
- video.pause();
-
- // Restore the playback rate
- video.playbackRate = goodTube_playbackSpeed;
- }
- // Otherwise, it's the main player and ads are showing
- else {
- // Play the video
- video.play();
-
- // Speed up to 2x (any faster is detected by Youtube)
- video.playbackRate = 2;
- }
- }
- }
- });
-
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_youtube_pauseMuteVideos_timeout);
-
- // Loop this function
- goodTube_youtube_pauseMuteVideos_timeout = setTimeout(goodTube_youtube_pauseMuteVideos, 100);
- }
-
- // Turn off autoplay
- function goodTube_youtube_turnOffAutoplay() {
- // If we've already turned off autoplay, just return
- if (goodTube_turnedOffAutoplay) {
- return;
- }
-
- // Target the autoplay button
- let autoplayButton = document.querySelector('#movie_player .ytp-autonav-toggle-button');
-
- // If we found it
- if (autoplayButton) {
- // Turn off autoplay
- if (autoplayButton.getAttribute('aria-checked') === 'true') {
- goodTube_helper_click(autoplayButton);
- }
-
- // Set a variable if autoplay has been turned off
- goodTube_turnedOffAutoplay = true;
- }
- }
-
- // Remove the "are you still watching" popup
- function goodTube_youtube_removeAreYouStillWatchingPopup() {
- // Get all the dialogue boxes
- let dialogueBoxes = document.querySelectorAll('yt-confirm-dialog-renderer');
-
- // For each dialogue box
- dialogueBoxes.forEach((dialogueBox) => {
- // If it has the correct text
- if (dialogueBox.innerHTML.indexOf('Video paused. Continue watching?') !== -1) {
- // Find the confirm button
- let confirmButton = dialogueBox.querySelector('#confirm-button');
-
- // If we found the confirm button
- if (confirmButton) {
- // Click it
- goodTube_helper_click(confirmButton);
- }
- }
- });
- }
-
- // Set the video aspect ratio
- function goodTube_youtube_setAspectRatio(widthRatio, heightRatio) {
- // Make sure we've been passed valid data
- if (!widthRatio || !heightRatio) {
- return;
- }
-
- // Target the aspect ratio element with the CSS variables
- let variableElement = document.querySelector('ytd-watch-flexy');
-
- // If we found the element, we're watching a video and the "hide and mute ads" fallback is inactive
- if (variableElement && goodTube_helper_watchingVideo() && !goodTube_fallback) {
- // Set the aspect ratio
- variableElement.style.setProperty("--ytd-watch-flexy-width-ratio", widthRatio);
- variableElement.style.setProperty("--ytd-watch-flexy-height-ratio", heightRatio);
- }
- }
-
- // Unset the video aspect ratio
- function goodTube_youtube_unsetAspectRatio() {
- // Target the aspect ratio element with the CSS variables
- let variableElement = document.querySelector('ytd-watch-flexy');
-
- // If we found the aspect ratio element
- if (variableElement) {
- // Remove the aspect ratio
- variableElement.style.removeProperty("--ytd-watch-flexy-width-ratio");
- variableElement.style.removeProperty("--ytd-watch-flexy-height-ratio");
- }
- }
-
-
- /* Player functions
- ------------------------------------------------------------------------------------------ */
- // Init player
- let goodTube_player_init_timeout = setTimeout(() => {}, 0);
- function goodTube_player_init() {
- // Get the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Get the video data to check loading state
- let videoData = false;
- if (goodTube_page_api && typeof goodTube_page_api.getVideoData === 'function') {
- videoData = goodTube_page_api.getVideoData();
- }
-
- // Keep trying to get the frame API until it exists
- if (!videoData) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_init_timeout);
-
- // Create a new timeout
- goodTube_player_init_timeout = setTimeout(goodTube_player_init, 100);
-
- return;
- }
-
- // Use a black background for the video player? (This is a setting)
- let backgroundColor = 'transparent';
- if (goodTube_blackBackground === 'true') {
- backgroundColor = '#000000';
- }
-
- // Add CSS styles for the player
- let style = document.createElement('style');
- style.textContent = `
+ `,console.log("[GoodTube] AI summaries removed"));let t=document.createElement("style");t.textContent=e,document.head.appendChild(t)}a(fa,"goodTube_youtube_hidePageElements");function ma(){if(be==="true")return;window.location.href.indexOf("/shorts")!==-1&&!Mo&&(window.location.href="https://youtube.com",Mo=!0),document.querySelectorAll("a:not(.goodTube_hidden)").forEach(i=>{i.href.indexOf("shorts/")!==-1&&(X(i),X(i.closest("ytd-video-renderer")),X(i.closest("ytd-compact-video-renderer")),X(i.closest("ytd-rich-grid-media")))}),document.querySelectorAll("yt-chip-cloud-chip-renderer:not(.goodTube_hidden):not(.goodTube_checked), yt-tab-shape:not(.goodTube_hidden):not(.goodTube_checked), ytd-guide-entry-renderer:not(.goodTube_checked)").forEach(i=>{i.innerHTML.toLowerCase().indexOf("shorts")!==-1&&X(i),i.classList.add("goodTube_checked")})}a(ma,"goodTube_youtube_hideShortsRealTime");function ga(){document.querySelectorAll("#description a, ytd-comments .yt-core-attributed-string a, ytm-expandable-video-description-body-renderer a, .comment-content a").forEach(t=>{!t.classList.contains("goodTube_timestampLink")&&t.getAttribute("href")&&t.getAttribute("href").indexOf(k.v)!==-1&&(t.classList.add("goodTube_timestampLink"),t.addEventListener("click",function(){let i=0,n=t.getAttribute("href").split("t=");typeof n[1]<"u"&&(i=parseFloat(n[1].replace("s",""))),Vo(i)}))})}a(ga,"goodTube_youtube_timestampLinks");let tt=setTimeout(()=>{},0);function Vt(){if(be==="true"&&window.location.href.indexOf("/shorts")!==-1||!U()){clearTimeout(tt),tt=setTimeout(Vt,100);return}document.querySelectorAll("video").forEach(t=>{_?!t.paused&&!t.closest("#movie_player")&&(t.muted=!0,t.volume=0,t.pause()):!t.paused&&(!Bt||!t.closest("#movie_player"))&&(t.muted=!0,t.volume=0,!Ot()||!t.closest("#movie_player")?(t.pause(),t.playbackRate=ye):(t.play(),t.playbackRate=2))}),clearTimeout(tt),tt=setTimeout(Vt,100)}a(Vt,"goodTube_youtube_pauseMuteVideos");function ba(){if(Do)return;let e=document.querySelector("#movie_player .ytp-autonav-toggle-button");e&&(e.getAttribute("aria-checked")==="true"&&W(e),Do=!0)}a(ba,"goodTube_youtube_turnOffAutoplay");function ya(){document.querySelectorAll("yt-confirm-dialog-renderer").forEach(t=>{if(t.innerHTML.indexOf("Video paused. Continue watching?")!==-1){let i=t.querySelector("#confirm-button");i&&W(i)}})}a(ya,"goodTube_youtube_removeAreYouStillWatchingPopup");function _a(e,t){if(!e||!t)return;let i=document.querySelector("ytd-watch-flexy");i&&U()&&!_&&(i.style.setProperty("--ytd-watch-flexy-width-ratio",e),i.style.setProperty("--ytd-watch-flexy-height-ratio",t))}a(_a,"goodTube_youtube_setAspectRatio");function ha(){let e=document.querySelector("ytd-watch-flexy");e&&(e.style.removeProperty("--ytd-watch-flexy-width-ratio"),e.style.removeProperty("--ytd-watch-flexy-height-ratio"))}a(ha,"goodTube_youtube_unsetAspectRatio");let No=setTimeout(()=>{},0);function Oo(){l=document.getElementById("movie_player");let e=!1;if(l&&typeof l.getVideoData=="function"&&(e=l.getVideoData()),!e){clearTimeout(No),No=setTimeout(Oo,100);return}let t="transparent";et==="true"&&(t="#000000");let i=document.createElement("style");i.textContent=`
/* Player wrapper */
#goodTube_playerWrapper {
border-radius: 12px;
- background: ` + backgroundColor + `;
+ background: `+t+`;
position: absolute;
top: 0;
left: 0;
@@ -721,1291 +134,15 @@
max-width: calc(177.77777778vh - var(--ytd-watch-flexy-masthead-height) * 1.7777777778 - var(--ytd-margin-6x) * 1.7777777778 - var(--ytd-watch-flexy-space-below-player) * 1.7777777778) !important;
min-width: calc(var(--ytd-watch-flexy-min-player-height) * 1.7777777778) !important;
}
- `;
- document.head.appendChild(style);
-
- // Setup player layout
- let playerWrapper = document.createElement('div');
- playerWrapper.id = 'goodTube_playerWrapper';
- playerWrapper.classList.add('goodTube_hidden');
-
- // Add player to the page
- document.body.appendChild(playerWrapper);
-
- // Add video iframe embed (via proxy iframe)
- let proxyIframe = document.createElement('iframe');
- proxyIframe.src = '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x65\x6e\x2e\x77\x69\x6b\x69\x70\x65\x64\x69\x61\x2e\x6f\x72\x67\x2f\x77\x69\x6b\x69\x2f\x46\x75\x63\x6b\x3f\x67\x6f\x6f\x64\x54\x75\x62\x65\x50\x72\x6f\x78\x79\x3d\x31';
- proxyIframe.setAttribute('width', '100%');
- proxyIframe.setAttribute('height', '100%');
- proxyIframe.setAttribute('frameborder', '0');
- proxyIframe.setAttribute('scrolling', 'yes');
- proxyIframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share');
- proxyIframe.setAttribute('referrerpolicy', 'strict-origin-when-cross-origin');
- proxyIframe.setAttribute('allowfullscreen', true);
- proxyIframe.style.display = 'none';
- playerWrapper.appendChild(proxyIframe);
-
- // Expose these globally
- goodTube_playerWrapper = playerWrapper;
- goodTube_player = proxyIframe;
-
- // Run the actions every 100ms
- goodTube_actions();
- setInterval(goodTube_actions, 100);
- }
-
- // Position and size the player
- let goodTube_clearedPlayer = false;
- function goodTube_player_positionAndSize() {
- // If the "hide and mute ads" fallback is inactive
- if (goodTube_fallback) {
- if (!goodTube_clearedPlayer) {
- // Hide and clear the embedded player
- goodTube_player_clear(true);
- goodTube_clearedPlayer = true;
- }
- }
- // Otherwise, the "hide and mute ads" fallback is inactive
- else {
- goodTube_clearedPlayer = false;
-
-
- // Get the Youtube player
- // We target 4 elements here, it seems to change for different users? Weird stuff.
- let youtubePlayer = document.querySelector('#player.ytd-watch-flexy');
-
- if (!youtubePlayer || youtubePlayer.offsetHeight <= 0) {
- youtubePlayer = document.querySelector('#ytd-player');
- }
-
- if (!youtubePlayer || youtubePlayer.offsetHeight <= 0) {
- youtubePlayer = document.querySelector('.player-size');
- }
-
- // This element helps during the loading of the page (if we use it we see the video a little sooner, which is nice)
- if (!youtubePlayer || youtubePlayer.offsetHeight <= 0) {
- youtubePlayer = document.querySelector('.html5-video-player');
- }
-
-
- // If we found the Youtube player
- if (youtubePlayer && youtubePlayer.offsetHeight > 0) {
- // Make our custom player match the position of the Youtube player
- // Note: Our custom player uses "position: absolute" so take into account the window scroll
- let rect = youtubePlayer.getBoundingClientRect();
- goodTube_playerWrapper.style.top = (rect.top + window.scrollY) + 'px';
- goodTube_playerWrapper.style.left = (rect.left + window.scrollX) + 'px';
-
- // Make our custom player match the size of the Youtube player
- goodTube_playerWrapper.style.width = youtubePlayer.offsetWidth + 'px';
- goodTube_playerWrapper.style.height = youtubePlayer.offsetHeight + 'px';
-
- // Show the GoodTube player
- goodTube_helper_showElement(goodTube_playerWrapper);
- }
- }
- }
-
- // Populate the playlist info
- let goodTube_player_populatePlaylistInfo_timeout = setTimeout(() => {}, 0);
- function goodTube_player_populatePlaylistInfo() {
- // Re fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Make sure we have access to the frame API
- if (typeof goodTube_page_api.getPlaylist === 'function' && typeof goodTube_page_api.getPlaylistIndex === 'function') {
- goodTube_playlist = goodTube_page_api.getPlaylist();
- goodTube_playlistIndex = goodTube_page_api.getPlaylistIndex();
-
- // If the playlist info isn't ready yet
- if (!goodTube_playlist) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_populatePlaylistInfo_timeout);
-
- // Try again
- goodTube_player_populatePlaylistInfo_timeout = setTimeout(goodTube_player_populatePlaylistInfo, 100);
- }
- }
- }
-
- // Load a video
- let goodTube_player_load_timeout = setTimeout(() => {}, 0);
- function goodTube_player_load() {
- // Reset the "hide and mute ads" state (this ensures the fallback will refresh for each new video)
- goodTube_hideAndMuteAds_state = '';
-
- // Pause the video first (this helps to prevent audio flashes)
- goodTube_player_pause();
-
-
- // Make sure the proxy iframe has loaded
- if (!goodTube_proxyIframeLoaded) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_load_timeout);
-
- // Create a new timeout to try again
- goodTube_player_load_timeout = setTimeout(goodTube_player_load, 100);
-
- // Don't do anything else
- return;
- }
-
-
- // Setup the starting time
- let startTime = 0;
-
- // Include the startime time from query params (if enabled)
- if (goodTube_alwaysStart === 'false') {
- if (typeof goodTube_getParams['t'] !== 'undefined') {
- startTime = parseFloat(goodTube_getParams['t'].replace('s', ''));
- }
- }
-
-
- // If we're viewing a playlist
- let playlist = 'false';
- if (typeof goodTube_getParams['i'] !== 'undefined' || typeof goodTube_getParams['index'] !== 'undefined' || typeof goodTube_getParams['list'] !== 'undefined') {
- // Populate the GET params below to let the iframe know we're viewing a playlist
- playlist = 'true';
-
- // Populate the playlist info
- goodTube_player_populatePlaylistInfo();
- }
- // Otherwise, remove playlist info
- else {
- goodTube_playlist = false;
- goodTube_playlistIndex = 0;
- }
-
- // If we're loading for the first time
- if (goodTube_firstLoad) {
- // If we're not viewing a video
- if (!goodTube_helper_watchingVideo()) {
- // Clear and hide the player
- goodTube_player_clear();
- }
-
- // Include the start time if it exists
- let startTimeParam = '';
- if (startTime > 0) {
- startTimeParam = '&start=' + startTime;
- }
-
- // Set the video source
- goodTube_player.contentWindow.postMessage('goodTube_src_https://www.youtube.com/embed/' + goodTube_getParams['v'] + '?goodTubeEmbed=1&autoplay=1&goodTube_playlist=' + playlist + '&goodTube_autoplay=' + goodTube_autoplay + '&goodTube_playbackSpeed=' + goodTube_playbackSpeed + '&goodTube_hideInfoCards=' + goodTube_hideInfoCards + '&goodTube_hideEndScreen=' + goodTube_hideEndScreen + startTimeParam, '*');
-
- // Indicate we've completed the first load
- goodTube_firstLoad = false;
- }
- // Otherwise, for all other loads
- else {
- // Load the video via the iframe api
- goodTube_player.contentWindow.postMessage('goodTube_load_' + goodTube_getParams['v'] + '|||' + startTime + '|||' + playlist, '*');
- }
-
- // Sync the starting time
- if (goodTube_alwaysStart === 'false') {
- goodTube_player_syncStartingTime();
- }
-
- // Play the video (this solves some edge cases)
- goodTube_player_play();
- }
-
- // Sync the starting time
- let goodTube_player_syncStartingTime_timeout = setTimeout(() => {}, 0);
- function goodTube_player_syncStartingTime() {
- // Make sure the player iframe has loaded
- if (!goodTube_playerIframeLoaded) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_syncStartingTime_timeout);
-
- // Create a new timeout to try again
- goodTube_player_syncStartingTime_timeout = setTimeout(goodTube_player_syncStartingTime, 100);
-
- // Don't do anything else
- return;
- }
-
-
- // Re fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Get the video data to check loading state and video id
- let videoData = false;
- let videoId = false;
- if (goodTube_page_api && typeof goodTube_page_api.getVideoData === 'function' && typeof goodTube_page_api.getCurrentTime === 'function') {
- videoData = goodTube_page_api.getVideoData();
- videoId = videoData.video_id;
- }
-
- // If there's no video data, no video id, or the id doesn't match the one in the query params yet (it hasn't loaded)
- if (!videoData || !videoId || videoId !== goodTube_getParams['v']) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_syncStartingTime_timeout);
-
- // Create a new timeout to try again
- goodTube_player_syncStartingTime_timeout = setTimeout(goodTube_player_syncStartingTime, 100);
-
- // Don't do anything else
- return;
- }
-
-
- // Setup the sync time
- let syncTime = Math.floor(goodTube_page_api.getCurrentTime());
-
- // If the sync time is greater than or equal to 10s (this accounts for some delayed loading time)
- if (syncTime >= 10) {
- // Sync our player
- goodTube_player_skipTo(syncTime, videoId);
- }
- }
-
- // Clear and hide the player
- function goodTube_player_clear(fallbackActive = false) {
- // If we're not in picture in picture mode
- if (!goodTube_pip) {
- // Clear the "hide and mute ads" fallback
- if (fallbackActive) {
- // Refetch the page api
- goodTube_page_api = document.getElementById('movie_player');
-
- // Make sure the API is all good
- if (goodTube_page_api && typeof goodTube_page_api.stopVideo === 'function') {
- // Stop the video
- goodTube_page_api.stopVideo();
- }
- }
- // Clear the regular player
- else {
- // Stop the video via the iframe api
- goodTube_player.contentWindow.postMessage('goodTube_stopVideo', '*');
- }
- }
-
- // Hide the player
- goodTube_helper_hideElement(goodTube_playerWrapper);
- }
-
- // Skip to time
- function goodTube_player_skipTo(time, videoId = '') {
- goodTube_player.contentWindow.postMessage('goodTube_skipTo_' + time + '|||' + videoId, '*');
- }
-
- // Pause
- function goodTube_player_pause() {
- goodTube_player.contentWindow.postMessage('goodTube_pause', '*');
- }
-
- // Play
- let goodTube_player_play_timeout = setTimeout(() => {}, 0);
- function goodTube_player_play() {
- // If the tab isn't in focus OR our player hasn't loaded
- if (!goodTube_tabInFocus || !goodTube_playerIframeLoaded) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_play_timeout);
-
- // Create a new timeout to try again
- goodTube_player_play_timeout = setTimeout(goodTube_player_play, 100);
-
- // Don't do anything else
- return;
- }
-
- // If the "hide and mute ads" fallback is disabled
- if (!goodTube_fallback) {
- goodTube_player.contentWindow.postMessage('goodTube_play|||' + goodTube_getParams['v'], '*');
- }
- // Otherwise, the "hide and mute ads" fallback is enabled
- else {
- // Re-fetch the page api
- goodTube_page_api = document.getElementById('movie_player');
-
- // Get the video data
- let videoData = false;
- if (goodTube_page_api && typeof goodTube_page_api.getVideoData === 'function') {
- videoData = goodTube_page_api.getVideoData();
- }
-
- // If the correct video hasn't loaded yet (based on the ID in the query params)
- if (!videoData || goodTube_getParams['v'] !== videoData.video_id) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_player_play_timeout);
-
- // Create a new timeout to try again
- goodTube_player_play_timeout = setTimeout(goodTube_player_play, 100);
-
- // Don't do anything else
- return;
- }
-
- // Make sure the video has not ended (this solves an edge case)
- let videoElement = document.querySelector('#movie_player video');
- if (videoElement) {
- if (videoElement.currentTime >= videoElement.duration) {
- return;
- }
- }
-
- // Play the video
- if (goodTube_page_api && typeof goodTube_page_api.playVideo === 'function') {
- // Wait 100ms (this solves an edge case)
- setTimeout(() => {
- // Force the video to play
- goodTube_page_api.playVideo();
- }, 100);
- }
- }
- }
-
-
- /* Keyboard shortcuts
- ------------------------------------------------------------------------------------------ */
- // Add keyboard shortcuts
- function goodTube_shortcuts_init() {
- // Add event listeners
- document.addEventListener('keydown', goodTube_shortcuts_keypress, true);
- document.addEventListener('keyup', goodTube_shortcuts_keypress, true);
- }
-
- // Define the keypress function for the event listeners
- function goodTube_shortcuts_keypress(event) {
- // If we're not watching a video OR the "hide and mute ads" fallback is active
- if (!goodTube_helper_watchingVideo() || goodTube_fallback) {
- // Don't do anything
- return;
- }
-
-
- // Define the shortcuts we allow
- let allowedShortcuts = [
- // Playback speed
- {
- key: '>',
- code: false,
- ctrl: false,
- shift: true,
- alt: false
- },
- {
- key: '<',
- code: false,
- ctrl: false,
- shift: true,
- alt: false
- },
-
- // Skip frame
- {
- key: ',',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '.',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Skip 5 seconds
- {
- key: 'arrowleft',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 'arrowright',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Skip 10 seconds
- {
- key: 'j',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 'l',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Play / pause
- {
- key: ' ',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 'k',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Mute
- {
- key: 'm',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Fullscreen
- {
- key: 'f',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Captions
- {
- key: 'c',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Text opacity
- {
- key: 'o',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Window opacity
- {
- key: 'w',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Font size
- {
- key: '=',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '-',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Panning (spherical videos)
- {
- key: 'w',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 'a',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 's',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: 'd',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Zooming (spherical videos)
- {
- key: '[',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: ']',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: false,
- code: 'numpadadd',
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: false,
- code: 'numpadsubtract',
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Skip to percentage
- {
- key: '0',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '1',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '2',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '3',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '4',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '5',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '6',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '7',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '8',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
- {
- key: '9',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Picture in picture
- {
- key: 'i',
- code: false,
- ctrl: false,
- shift: false,
- alt: false
- },
-
- // Seek chapters
- {
- key: 'arrowleft',
- code: false,
- ctrl: true,
- shift: false,
- alt: false
- },
- {
- key: 'arrowright',
- code: false,
- ctrl: true,
- shift: false,
- alt: false
- }
- ];
-
-
- // Which key was pressed?
- let keyPressed = event.key.toLowerCase();
- let codePressed = event.code.toLowerCase();
-
- // Is ctrl or meta (mac) pressed?
- let ctrlPressed = event.ctrlKey;
- if (event.metaKey) {
- ctrlPressed = event.metaKey;
- }
-
- // Is shift pressed?
- let shiftPressed = event.shiftKey;
-
- // Is alt pressed?
- let altPressed = event.altKey;
-
-
- // Ensure we've pressed an allowed shortcut
- let allowKeypress = false;
- allowedShortcuts.forEach((allowedShortcut) => {
- if (
- (keyPressed === allowedShortcut.key || codePressed === allowedShortcut.code) &&
- ctrlPressed === allowedShortcut.ctrl &&
- shiftPressed === allowedShortcut.shift &&
- altPressed === allowedShortcut.alt
- ) {
- allowKeypress = true;
- }
- });
-
-
- // If we've allowed this keypress (because the shortcut was valid)
- if (allowKeypress) {
- // Get the currently focused element
- let focusedElement = event.srcElement;
- let focusedElement_tag = false;
- let focusedElement_id = false;
- if (focusedElement) {
- if (typeof focusedElement.nodeName !== 'undefined') {
- focusedElement_tag = focusedElement.nodeName.toLowerCase();
- }
-
- if (typeof focusedElement.getAttribute !== 'undefined') {
- focusedElement_id = focusedElement.getAttribute('id');
- }
- }
-
- // If we're not focused on a HTML form element
- if (
- !focusedElement ||
- (
- focusedElement_tag.indexOf('input') === -1 &&
- focusedElement_tag.indexOf('label') === -1 &&
- focusedElement_tag.indexOf('select') === -1 &&
- focusedElement_tag.indexOf('textarea') === -1 &&
- focusedElement_tag.indexOf('fieldset') === -1 &&
- focusedElement_tag.indexOf('legend') === -1 &&
- focusedElement_tag.indexOf('datalist') === -1 &&
- focusedElement_tag.indexOf('output') === -1 &&
- focusedElement_tag.indexOf('option') === -1 &&
- focusedElement_tag.indexOf('optgroup') === -1 &&
- focusedElement_id !== 'contenteditable-root'
- )
- ) {
- // Prevent default actions
- event.preventDefault();
- event.stopImmediatePropagation();
-
- // Pass the keyboard shortcut to the iframe
- goodTube_player.contentWindow.postMessage('goodTube_shortcut_' + event.type + '_' + event.key + '_' + event.keyCode + '_' + event.ctrlKey + '_' + event.metaKey + '_' + event.shiftKey + '_' + event.altKey, '*');
- }
- }
- }
-
-
- /* Navigation
- ------------------------------------------------------------------------------------------ */
- // Play the next video
- function goodTube_nav_next(shuffleLoopMethod = false) {
- // Re fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Make sure it exists
- if (goodTube_page_api && typeof goodTube_page_api.nextVideo === 'function') {
- // Play the previous video
- goodTube_page_api.nextVideo();
- }
-
- // Debug message
- console.log('[GoodTube] Playing next video...');
- }
-
- // Play the previous video
- function goodTube_nav_prev() {
- // Re fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Make sure it exists
- if (goodTube_page_api && typeof goodTube_page_api.previousVideo === 'function') {
- // Play the previous video
- goodTube_page_api.previousVideo();
- }
-
- // Debug message
- console.log('[GoodTube] Playing previous video...');
- }
-
- // Video has ended
- function goodTube_nav_videoEnded() {
- // Populate the playlist info
- goodTube_player_populatePlaylistInfo();
-
- // If (autoplay is enabled) OR (we're viewing a playlist AND we're not on the last video)
- if (
- goodTube_autoplay === 'true'
- ||
- (goodTube_playlist && (goodTube_playlistIndex < (goodTube_playlist.length - 1)))
- ) {
- // Play the next video
- goodTube_nav_next();
- }
- }
-
- // Show or hide the end screen (based on autoplay, not the setting)
- function goodTube_nav_showHideEndScreen() {
- // Re fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Show the end screen
- let hideEndScreen = false;
-
- // If autoplay is on, hide the end screen
- if (goodTube_autoplay === 'true') {
- hideEndScreen = true;
- }
-
- // Otherwise, if we're viewing a playlist
- else if (goodTube_playlist) {
- // Hide the end screen
- hideEndScreen = true;
-
- // If we're on the last video
- if (goodTube_playlistIndex === (goodTube_playlist.length - 1)) {
- // Show the end screen
- hideEndScreen = false;
- }
- }
-
- // Hide the end screen
- if (hideEndScreen) {
- goodTube_player.contentWindow.postMessage('goodTube_endScreen_hide', '*');
- }
- // Otherwise show the end screen
- else {
- goodTube_player.contentWindow.postMessage('goodTube_endScreen_show', '*');
- }
- }
-
-
- /* Usage stats
- ------------------------------------------------------------------------------------------
- Don't worry everyone - these are just simple counters that let me know the following;
- - Daily unique users
- - Total unique users
- - Daily videos played
- - Total videos played
-
- This is only in here so I can have some fun and see how many people use this thing I made **no private info is tracked**
- */
-
- // Count unique users
- function goodTube_stats_user() {
- /* Get today's date as yyyy-mm-dd (UTC time)
- -------------------------------------------------- */
- let date_local = new Date();
- let date_utc = Date.UTC(date_local.getUTCFullYear(), date_local.getUTCMonth(), date_local.getUTCDate(), date_local.getUTCHours(), date_local.getUTCMinutes(), date_local.getUTCSeconds());
- let date_utc_formatted = new Date(date_utc);
- let date_string = date_utc_formatted.toISOString().split('T')[0];
-
-
- /* Daily unique users
- -------------------------------------------------- */
- // If there's no cookie
- if (!goodTube_helper_getCookie('goodTube_uniqueUserStat_' + date_string)) {
- // Count
- fetch(
- '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x75\x73\x65\x72\x73\x5f\x64\x61\x69\x6c\x79\x2e\x70\x68\x70',
- {
- referrerPolicy: 'no-referrer'
- }
- );
-
- // Set a cookie (2 days exp time - to limit the cookies we create)
- goodTube_helper_setCookie('goodTube_uniqueUserStat_' + date_string, 'true', 2);
- }
-
-
- /* Total unique users
- -------------------------------------------------- */
- // If there's no cookie
- if (!goodTube_helper_getCookie('goodTube_uniqueUserStat')) {
- // Count
- fetch(
- '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x75\x73\x65\x72\x73\x5f\x74\x6f\x74\x61\x6c\x2e\x70\x68\x70',
- {
- referrerPolicy: 'no-referrer'
- }
- );
-
- // Set a cookie
- goodTube_helper_setCookie('goodTube_uniqueUserStat', 'true');
- }
- }
-
- // Count videos played
- function goodTube_stats_video() {
- /* Videos played (combined total and daily)
- -------------------------------------------------- */
- // Count
- fetch(
- '\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x76\x69\x64\x65\x6f\x73\x2e\x70\x68\x70',
- {
- referrerPolicy: 'no-referrer'
- }
- );
- }
-
-
- /* Core functions
- ------------------------------------------------------------------------------------------ */
- // Init
- let goodTube_initiated = false;
- let goodTube_init_timeout = setTimeout(() => {}, 0);
- function goodTube_init(retrying = false) {
- // If we're not retrying
- if (!retrying) {
- // Debug message
- if (window.top === window.self) {
- console.log('\n==================================================\n ______ ________ __\n / ____/___ ____ ____/ /_ __/_ __/ /_ ___\n / / __/ __ \\/ __ \\/ __ / / / / / / / __ \\/ _ \\\n / /_/ / /_/ / /_/ / /_/ / / / / /_/ / /_/ / __/\n \\____/\\____/\\____/\\____/ /_/ \\____/_____/\\___/\n\n==================================================');
- console.log('[GoodTube] Initiating...');
- }
-
- // Listen for messages from the iframes
- window.addEventListener('message', goodTube_receiveMessage);
-
- // Mute and pause all Youtube videos
- goodTube_youtube_pauseMuteVideos();
-
- // Init the rest once the DOM is ready
- document.addEventListener('DOMContentLoaded', goodTube_init_domReady);
-
- // Also check if the DOM is already loaded, as if it is, the above event listener will not trigger
- if (document.readyState === 'interactive' || document.readyState === 'complete') {
- goodTube_init_domReady();
- }
- }
-
- // And try this to check if the DOM is ready, seems to be the only reliable method in all browsers (which is insane, I know...thanks Safari)
- if (!document.body || !document.head) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_init_timeout);
-
- // Create a new timeout
- goodTube_init_timeout = setTimeout(() => { goodTube_init(true); }, 1);
- }
- // Otherwise, the DOM is ready
- else {
- goodTube_init_domReady();
- }
- }
-
- // Init when DOM is ready
- function goodTube_init_domReady() {
- // Sanity check - only do this once (this fixes known load issues in Firefox)
- if (goodTube_initiated) {
- return;
- }
- goodTube_initiated = true;
-
- // Check the tab focus state
- goodTube_checkTabFocus();
-
- // Add a CSS class to show or hide elements
- goodTube_helper_showHide_init();
-
- // Hide page elements
- goodTube_youtube_hidePageElements();
-
- // Init our player
- goodTube_player_init();
-
- // Init the "hide and mute ads" fallback
- goodTube_hideAndMuteAdsFallback_init();
-
- // Usage stats
- goodTube_stats_user();
-
- // Keyboard shortcuts
- goodTube_shortcuts_init();
-
- // Init the menu
- goodTube_menu();
- }
-
- // Listen for messages from the iframe
- let goodTube_receiveMessage_timeout = setTimeout(() => {}, 0);
- function goodTube_receiveMessage(event) {
- // Make sure some data exists
- if (typeof event.data !== 'string') {
- return;
- }
-
- // Make sure the DOM is ready, if not retry (this ensures that the message will fire eventually)
- // Use this method to check if the DOM is ready, seems to be the only reliable method in all browsers (which is insane, I know...thanks Safari)
- if (!document.body || !document.head) {
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_receiveMessage_timeout);
-
- // Create a new timeout
- goodTube_receiveMessage_timeout = setTimeout(() => { goodTube_receiveMessage(event); }, 100);
- }
-
- // Proxy iframe has loaded
- else if (event.data === 'goodTube_proxyIframe_loaded') {
- goodTube_proxyIframeLoaded = true;
- }
-
- // Player iframe has loaded
- else if (event.data === 'goodTube_playerIframe_loaded') {
- goodTube_playerIframeLoaded = true;
-
- // Show the player iframe
- goodTube_player.style.display = 'block';
- }
-
- // Picture in picture
- if (event.data.indexOf('goodTube_pip_') !== -1) {
- let pipEnabled = event.data.replace('goodTube_pip_', '');
-
- if (pipEnabled === 'true') {
- goodTube_pip = true;
- }
- else {
- goodTube_pip = false;
-
- // If we're not viewing a video
- if (typeof goodTube_getParams['v'] === 'undefined') {
- // Clear the player
- goodTube_player_clear();
- }
- }
- }
-
- // Save the playback speed as a cookie
- else if (event.data.indexOf('goodTube_playbackSpeed_') !== -1) {
- goodTube_helper_setCookie('goodTube_playbackSpeed', event.data.replace('goodTube_playbackSpeed_', ''));
- goodTube_playbackSpeed = event.data.replace('goodTube_playbackSpeed_', '');
- }
-
- // Previous video
- else if (event.data === 'goodTube_prevVideo') {
- goodTube_nav_prev();
- }
-
- // Next video
- else if (event.data === 'goodTube_nextVideo') {
- goodTube_nav_next();
- }
-
- // Video has ended
- else if (event.data === 'goodTube_videoEnded') {
- goodTube_nav_videoEnded();
- }
-
- // Theater mode (toggle) - this should only work when not in fullscreen
- else if (event.data === 'goodTube_theater' && !document.fullscreenElement) {
- // Find the theater button
- let theaterButton = document.querySelector('.ytp-size-button');
-
- // If we found the theater button
- if (theaterButton) {
- // Click it
- goodTube_helper_click(theaterButton);
- }
- }
-
- // Autoplay
- else if (event.data === 'goodTube_autoplay_false') {
- goodTube_helper_setCookie('goodTube_autoplay', 'false');
- goodTube_autoplay = 'false';
- }
- else if (event.data === 'goodTube_autoplay_true') {
- goodTube_helper_setCookie('goodTube_autoplay', 'true');
- goodTube_autoplay = 'true';
- }
-
- // Sync main player (only if we're viewing a video page AND the "hide and mute ads" fallback is inactive)
- else if (event.data.indexOf('goodTube_syncMainPlayer_') !== -1 && goodTube_helper_watchingVideo() && !goodTube_fallback) {
- // Parse the data
- let syncTime = parseFloat(event.data.replace('goodTube_syncMainPlayer_', ''));
-
- // Target the youtube video element
- let youtubeVideoElement = document.querySelector('#movie_player video');
-
- // Re-fetch the page API
- goodTube_page_api = document.getElementById('movie_player');
-
- // Make sure the API is all good
- if (!goodTube_page_api || typeof goodTube_page_api.seekTo !== 'function' || typeof goodTube_page_api.playVideo !== 'function' || typeof goodTube_page_api.mute !== 'function' || typeof goodTube_page_api.setVolume !== 'function') {
- return;
- }
-
- // If we found the video element
- // AND we've not already synced to this point (this stops it continuing to sync when ended for no reason, we also need to round it down as it seems to be unreliable)
- // AND ads are not showing (we don't want to touch the the time when ads are playing, this triggers detection)
- if (youtubeVideoElement && Math.floor(youtubeVideoElement.currentTime) !== Math.floor(syncTime) && !goodTube_helper_adsShowing()) {
- // Set a variable to indicate we're syncing the player (this stops the automatic pausing of all videos)
- goodTube_syncingPlayer = true;
-
- // Play the video via the page API (this is the only reliable way)
- goodTube_page_api.playVideo();
-
- // Sync the current time using the page API - 500ms (this is the only reliable way)
- goodTube_page_api.seekTo((syncTime - .5));
-
- // Then mute the video via the page API (this helps to prevent audio flashes)
- goodTube_page_api.mute();
- goodTube_page_api.setVolume(0);
-
- // Then mute the video via HTML (playing it unmutes it for some reason)
- youtubeVideoElement.volume = 0;
- youtubeVideoElement.muted = true;
-
- // Clear timeout first to solve memory leak issues
- clearTimeout(goodTube_receiveMessage_timeout);
-
- // After 1000ms stop syncing (and let the pause actions handle the pausing)
- goodTube_receiveMessage_timeout = setTimeout(() => {
- goodTube_syncingPlayer = false;
- }, 1000);
- }
- }
-
- // Enable "hide and mute ads" fallback
- else if (event.data === 'goodTube_fallback_enable') {
- goodTube_fallback = true;
-
- // Add a class to the
- if (document.body && !document.body.classList.contains('goodTube_fallback')) {
- document.body.classList.add('goodTube_fallback');
- }
-
- // Unset the aspect ratio
- goodTube_youtube_unsetAspectRatio();
-
- // Sync the autoplay
- goodTube_hideAndMuteAdsFallback_syncAutoplay();
-
- // Play the video (this solves some edge cases)
- goodTube_player_play();
-
- // If we're in fullscreen already
- if (document.fullscreenElement) {
- // Exit fullscreen
- document.exitFullscreen();
-
- // Fullscreen the normal Youtube player (wait 100ms, this delay is required because browsers animate fullscreen animations and we can't change this)
- window.setTimeout(() => {
- let fullscreenButton = document.querySelector('.ytp-fullscreen-button');
- if (fullscreenButton) {
- goodTube_helper_click(fullscreenButton);
- }
- }, 100);
- }
- }
-
- // Disable "hide and mute ads" fallback
- else if (event.data === 'goodTube_fallback_disable') {
- goodTube_fallback = false;
-
- // Remove the class from the
- if (document.body && document.body.classList.contains('goodTube_fallback')) {
- document.body.classList.remove('goodTube_fallback');
- }
-
- // If we're in fullscreen already
- if (document.fullscreenElement) {
- // Exit fullscreen
- document.exitFullscreen();
-
- // Fullscreen the normal Youtube player (wait 100ms, this delay is required because browsers animate fullscreen animations and we can't change this)
- window.setTimeout(() => {
- goodTube_player.contentWindow.postMessage('goodTube_fullscreen', '*');
- }, 100);
- }
- }
-
- // Sync the aspect ratio
- else if (event.data.indexOf('goodTube_syncAspectRatio_') !== -1) {
- let aspectRatio = event.data.replace('goodTube_syncAspectRatio_', '').split('_');
- goodTube_youtube_setAspectRatio(aspectRatio[0], aspectRatio[1]);
- }
- }
-
- // Actions
- function goodTube_actions() {
- // Get the previous and current URL
-
- // Remove hashes, these mess with things sometimes
- // Also remove "index="
- let previousUrl = goodTube_previousUrl;
- if (previousUrl) {
- previousUrl = previousUrl.split('#')[0];
- previousUrl = previousUrl.split('index=')[0];
- }
-
- let currentUrl = window.location.href;
- if (currentUrl) {
- currentUrl = currentUrl.split('#')[0];
- currentUrl = currentUrl.split('index=')[0];
- }
-
- // If the URL has changed (this will always fire on first page load)
- if (previousUrl !== currentUrl) {
- // The URL has changed, so setup our player
- // ----------------------------------------------------------------------------------------------------
- // Setup GET parameters
- goodTube_getParams = goodTube_helper_setupGetParams();
-
- // If we're viewing a video
- if (goodTube_helper_watchingVideo()) {
- // Load the video
- goodTube_player_load();
-
- // Usage stats
- goodTube_stats_video();
- }
- // Otherwise if we're not viewing a video
- else {
- // Clear the player
- goodTube_player_clear();
- }
-
- // Set the previous URL (which pauses this function until the URL changes again)
- goodTube_previousUrl = window.location.href;
- }
-
- // If we're viewing a video
- if (goodTube_helper_watchingVideo()) {
- // Show or hide the end screen (based on autoplay, not the setting)
- goodTube_nav_showHideEndScreen();
-
- // Support timestamp links
- goodTube_youtube_timestampLinks();
-
- // If the "hide and mute ads" fallback is inactive
- if (!goodTube_fallback) {
- // Turn off autoplay
- goodTube_youtube_turnOffAutoplay();
- }
-
- // Remove the "are you still watching" popup
- goodTube_youtube_removeAreYouStillWatchingPopup();
-
- // Position and size the player
- goodTube_player_positionAndSize();
-
- // Check to enable or disable the "hide and mute ads" fallback overlay
- goodTube_hideAndMuteAdsFallback_check();
- }
-
- // Hide shorts (real time)
- goodTube_youtube_hideShortsRealTime();
- }
-
- // Init menu
- function goodTube_menu() {
- // Create the menu container
- let menuContainer = document.createElement('div');
-
- // Add the menu container to the page
- document.body.appendChild(menuContainer);
-
- // Configure the settings to show their actual values
- let shortsEnabled = ' checked';
- if (goodTube_shorts === 'true') {
- shortsEnabled = '';
- }
-
- let hideInfoCards = '';
- if (goodTube_hideInfoCards === 'true') {
- hideInfoCards = ' checked';
- }
-
- let hideEndScreen = '';
- if (goodTube_hideEndScreen === 'true') {
- hideEndScreen = ' checked';
- }
-
- let hideSuggestedVideos = '';
- if (goodTube_hideSuggestedVideos === 'true') {
- hideSuggestedVideos = ' checked';
- }
-
- let hideComments = '';
- if (goodTube_hideComments === 'true') {
- hideComments = ' checked';
- }
-
- let hideAiSummaries = '';
- if (goodTube_hideAiSummaries === 'true') {
- hideAiSummaries = ' checked';
- }
-
- let alwaysStart = '';
- if (goodTube_alwaysStart === 'true') {
- alwaysStart = ' checked';
- }
-
- let blackBackground = '';
- if (goodTube_blackBackground === 'true') {
- blackBackground = ' checked';
- }
-
- // Add content to the menu container
- goodTube_helper_innerHTML(menuContainer, `
+ `,document.head.appendChild(i);let n=document.createElement("div");n.id="goodTube_playerWrapper",n.classList.add("goodTube_hidden"),document.body.appendChild(n);let s=document.createElement("iframe");s.src="https://en.wikipedia.org/wiki/Fuck?goodTubeProxy=1",s.setAttribute("width","100%"),s.setAttribute("height","100%"),s.setAttribute("frameborder","0"),s.setAttribute("scrolling","yes"),s.setAttribute("allow","accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"),s.setAttribute("referrerpolicy","strict-origin-when-cross-origin"),s.setAttribute("allowfullscreen",!0),s.style.display="none",n.appendChild(s),ae=n,P=s,jo(),setInterval(jo,100)}a(Oo,"goodTube_player_init");let Ft=!1;function Ta(){if(_)Ft||(it(!0),Ft=!0);else{Ft=!1;let e=document.querySelector("#player.ytd-watch-flexy");if((!e||e.offsetHeight<=0)&&(e=document.querySelector("#ytd-player")),(!e||e.offsetHeight<=0)&&(e=document.querySelector(".player-size")),(!e||e.offsetHeight<=0)&&(e=document.querySelector(".html5-video-player")),e&&e.offsetHeight>0){let t=e.getBoundingClientRect();ae.style.top=t.top+window.scrollY+"px",ae.style.left=t.left+window.scrollX+"px",ae.style.width=e.offsetWidth+"px",ae.style.height=e.offsetHeight+"px",We(ae)}}}a(Ta,"goodTube_player_positionAndSize");let Bo=setTimeout(()=>{},0);function Ut(){l=document.getElementById("movie_player"),typeof l.getPlaylist=="function"&&typeof l.getPlaylistIndex=="function"&&(ne=l.getPlaylist(),Ke=l.getPlaylistIndex(),ne||(clearTimeout(Bo),Bo=setTimeout(Ut,100)))}a(Ut,"goodTube_player_populatePlaylistInfo");let Po=setTimeout(()=>{},0);function zo(){if(re="",xa(),!Ro){clearTimeout(Po),Po=setTimeout(zo,100);return}let e=0;Ve==="false"&&typeof k.t<"u"&&(e=parseFloat(k.t.replace("s","")));let t="false";if(typeof k.i<"u"||typeof k.index<"u"||typeof k.list<"u"?(t="true",Ut()):(ne=!1,Ke=0),Co){U()||it();let i="";e>0&&(i="&start="+e),P.contentWindow.postMessage("goodTube_src_https://www.youtube.com/embed/"+k.v+"?goodTubeEmbed=1&autoplay=1&goodTube_playlist="+t+"&goodTube_autoplay="+Z+"&goodTube_playbackSpeed="+ye+"&goodTube_hideInfoCards="+Pe+"&goodTube_hideEndScreen="+ze+i,"*"),Co=!1}else P.contentWindow.postMessage("goodTube_load_"+k.v+"|||"+e+"|||"+t,"*");Ve==="false"&&Ht(),nt()}a(zo,"goodTube_player_load");let ot=setTimeout(()=>{},0);function Ht(){if(!Pt){clearTimeout(ot),ot=setTimeout(Ht,100);return}l=document.getElementById("movie_player");let e=!1,t=!1;if(l&&typeof l.getVideoData=="function"&&typeof l.getCurrentTime=="function"&&(e=l.getVideoData(),t=e.video_id),!e||!t||t!==k.v){clearTimeout(ot),ot=setTimeout(Ht,100);return}let i=Math.floor(l.getCurrentTime());i>=10&&Vo(i,t)}a(Ht,"goodTube_player_syncStartingTime");function it(e=!1){Be||(e?(l=document.getElementById("movie_player"),l&&typeof l.stopVideo=="function"&&l.stopVideo()):P.contentWindow.postMessage("goodTube_stopVideo","*")),X(ae)}a(it,"goodTube_player_clear");function Vo(e,t=""){P.contentWindow.postMessage("goodTube_skipTo_"+e+"|||"+t,"*")}a(Vo,"goodTube_player_skipTo");function xa(){P.contentWindow.postMessage("goodTube_pause","*")}a(xa,"goodTube_player_pause");let at=setTimeout(()=>{},0);function nt(){if(!zt||!Pt){clearTimeout(at),at=setTimeout(nt,100);return}if(!_)P.contentWindow.postMessage("goodTube_play|||"+k.v,"*");else{l=document.getElementById("movie_player");let e=!1;if(l&&typeof l.getVideoData=="function"&&(e=l.getVideoData()),!e||k.v!==e.video_id){clearTimeout(at),at=setTimeout(nt,100);return}let t=document.querySelector("#movie_player video");if(t&&t.currentTime>=t.duration)return;l&&typeof l.playVideo=="function"&&setTimeout(()=>{l.playVideo()},100)}}a(nt,"goodTube_player_play");function va(){document.addEventListener("keydown",Fo,!0),document.addEventListener("keyup",Fo,!0)}a(va,"goodTube_shortcuts_init");function Fo(e){if(!U()||_)return;let t=[{key:">",code:!1,ctrl:!1,shift:!0,alt:!1},{key:"<",code:!1,ctrl:!1,shift:!0,alt:!1},{key:",",code:!1,ctrl:!1,shift:!1,alt:!1},{key:".",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"arrowleft",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"arrowright",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"j",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"l",code:!1,ctrl:!1,shift:!1,alt:!1},{key:" ",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"k",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"m",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"f",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"c",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"o",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"w",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"=",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"-",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"w",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"a",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"s",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"d",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"[",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"]",code:!1,ctrl:!1,shift:!1,alt:!1},{key:!1,code:"numpadadd",ctrl:!1,shift:!1,alt:!1},{key:!1,code:"numpadsubtract",ctrl:!1,shift:!1,alt:!1},{key:"0",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"1",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"2",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"3",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"4",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"5",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"6",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"7",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"8",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"9",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"i",code:!1,ctrl:!1,shift:!1,alt:!1},{key:"arrowleft",code:!1,ctrl:!0,shift:!1,alt:!1},{key:"arrowright",code:!1,ctrl:!0,shift:!1,alt:!1}],i=e.key.toLowerCase(),n=e.code.toLowerCase(),s=e.ctrlKey;e.metaKey&&(s=e.metaKey);let c=e.shiftKey,g=e.altKey,w=!1;if(t.forEach(x=>{(i===x.key||n===x.code)&&s===x.ctrl&&c===x.shift&&g===x.alt&&(w=!0)}),w){let x=e.srcElement,A=!1,oe=!1;x&&(typeof x.nodeName<"u"&&(A=x.nodeName.toLowerCase()),typeof x.getAttribute<"u"&&(oe=x.getAttribute("id"))),(!x||A.indexOf("input")===-1&&A.indexOf("label")===-1&&A.indexOf("select")===-1&&A.indexOf("textarea")===-1&&A.indexOf("fieldset")===-1&&A.indexOf("legend")===-1&&A.indexOf("datalist")===-1&&A.indexOf("output")===-1&&A.indexOf("option")===-1&&A.indexOf("optgroup")===-1&&oe!=="contenteditable-root")&&(e.preventDefault(),e.stopImmediatePropagation(),P.contentWindow.postMessage("goodTube_shortcut_"+e.type+"_"+e.key+"_"+e.keyCode+"_"+e.ctrlKey+"_"+e.metaKey+"_"+e.shiftKey+"_"+e.altKey,"*"))}}a(Fo,"goodTube_shortcuts_keypress");function Uo(e=!1){l=document.getElementById("movie_player"),l&&typeof l.nextVideo=="function"&&l.nextVideo(),console.log("[GoodTube] Playing next video...")}a(Uo,"goodTube_nav_next");function ka(){l=document.getElementById("movie_player"),l&&typeof l.previousVideo=="function"&&l.previousVideo(),console.log("[GoodTube] Playing previous video...")}a(ka,"goodTube_nav_prev");function wa(){Ut(),(Z==="true"||ne&&Ke{},0);function Go(e=!1){e||(window.top===window.self&&(console.log(`
+==================================================
+ ______ ________ __
+ / ____/___ ____ ____/ /_ __/_ __/ /_ ___
+ / / __/ __ \\/ __ \\/ __ / / / / / / / __ \\/ _ \\
+ / /_/ / /_/ / /_/ / /_/ / / / / /_/ / /_/ / __/
+ \\____/\\____/\\____/\\____/ /_/ \\____/_____/\\___/
+
+==================================================`),console.log("[GoodTube] Initiating...")),window.addEventListener("message",Qo),Vt(),document.addEventListener("DOMContentLoaded",Yt),(document.readyState==="interactive"||document.readyState==="complete")&&Yt()),!document.body||!document.head?(clearTimeout(Yo),Yo=setTimeout(()=>{Go(!0)},1)):Yt()}a(Go,"goodTube_init");function Yt(){Ho||(Ho=!0,La(),ca(),fa(),Oo(),Da(),Ea(),va(),Ia())}a(Yt,"goodTube_init_domReady");let rt=setTimeout(()=>{},0);function Qo(e){if(typeof e.data=="string"){if(!document.body||!document.head?(clearTimeout(rt),rt=setTimeout(()=>{Qo(e)},100)):e.data==="goodTube_proxyIframe_loaded"?Ro=!0:e.data==="goodTube_playerIframe_loaded"&&(Pt=!0,P.style.display="block"),e.data.indexOf("goodTube_pip_")!==-1)e.data.replace("goodTube_pip_","")==="true"?Be=!0:(Be=!1,typeof k.v>"u"&&it());else if(e.data.indexOf("goodTube_playbackSpeed_")!==-1)m("goodTube_playbackSpeed",e.data.replace("goodTube_playbackSpeed_","")),ye=e.data.replace("goodTube_playbackSpeed_","");else if(e.data==="goodTube_prevVideo")ka();else if(e.data==="goodTube_nextVideo")Uo();else if(e.data==="goodTube_videoEnded")wa();else if(e.data==="goodTube_theater"&&!document.fullscreenElement){let t=document.querySelector(".ytp-size-button");t&&W(t)}else if(e.data==="goodTube_autoplay_false")m("goodTube_autoplay","false"),Z="false";else if(e.data==="goodTube_autoplay_true")m("goodTube_autoplay","true"),Z="true";else if(e.data.indexOf("goodTube_syncMainPlayer_")!==-1&&U()&&!_){let t=parseFloat(e.data.replace("goodTube_syncMainPlayer_","")),i=document.querySelector("#movie_player video");if(l=document.getElementById("movie_player"),!l||typeof l.seekTo!="function"||typeof l.playVideo!="function"||typeof l.mute!="function"||typeof l.setVolume!="function")return;i&&Math.floor(i.currentTime)!==Math.floor(t)&&!Ot()&&(Bt=!0,l.playVideo(),l.seekTo(t-.5),l.mute(),l.setVolume(0),i.volume=0,i.muted=!0,clearTimeout(rt),rt=setTimeout(()=>{Bt=!1},1e3))}else if(e.data==="goodTube_fallback_enable")_=!0,document.body&&!document.body.classList.contains("goodTube_fallback")&&document.body.classList.add("goodTube_fallback"),ha(),Ko(),nt(),document.fullscreenElement&&(document.exitFullscreen(),window.setTimeout(()=>{let t=document.querySelector(".ytp-fullscreen-button");t&&W(t)},100));else if(e.data==="goodTube_fallback_disable")_=!1,document.body&&document.body.classList.contains("goodTube_fallback")&&document.body.classList.remove("goodTube_fallback"),document.fullscreenElement&&(document.exitFullscreen(),window.setTimeout(()=>{P.contentWindow.postMessage("goodTube_fullscreen","*")},100));else if(e.data.indexOf("goodTube_syncAspectRatio_")!==-1){let t=e.data.replace("goodTube_syncAspectRatio_","").split("_");_a(t[0],t[1])}}}a(Qo,"goodTube_receiveMessage");function jo(){let e=Lo;e&&(e=e.split("#")[0],e=e.split("index=")[0]);let t=window.location.href;t&&(t=t.split("#")[0],t=t.split("index=")[0]),e!==t&&(k=Io(),U()?(zo(),Sa()):it(),Lo=window.location.href),U()&&(Aa(),ga(),_||ba(),ya(),Ta(),Ma()),ma()}a(jo,"goodTube_actions");function Ia(){let e=document.createElement("div");document.body.appendChild(e);let t=" checked";be==="true"&&(t="");let i="";Pe==="true"&&(i=" checked");let n="";ze==="true"&&(n=" checked");let s="";Je==="true"&&(s=" checked");let c="";Xe==="true"&&(c=" checked");let g="";$e==="true"&&(g=" checked");let w="";Ve==="true"&&(w=" checked");let x="";et==="true"&&(x=" checked"),Ze(e,`
Donate now
@@ -2129,11 +266,7 @@
- `);
-
- // Style the menu
- let style = document.createElement('style');
- style.textContent = `
+ `);let A=document.createElement("style");A.textContent=`
/* Menu button
---------------------------------------------------------------------------------------------------- */
.goodTube_menuButton {
@@ -2459,238 +592,7 @@
margin-bottom: 16px;
height: 128px;
}
- `;
- document.head.appendChild(style);
-
-
- /* Menu button
- -------------------------------------------------- */
- // Target the elements
- let menuButton = document.querySelector('.goodTube_menuButton');
- let menuClose = document.querySelector('.goodTube_menuClose');
-
- // Support the close button
- if (menuClose) {
- menuClose.addEventListener('click', () => {
- menuButton.remove();
- menuClose.remove();
- });
- }
-
-
- /* Modal
- -------------------------------------------------- */
- // Target the elements
- let modal = document.querySelector('.goodTube_modal');
- let modalOverlay = document.querySelector('.goodTube_modal .goodTube_modal_overlay');
- let modalCloseButton = document.querySelector('.goodTube_modal .goodTube_modal_closeButton');
-
- // Open the modal
- if (menuButton) {
- menuButton.addEventListener('click', () => {
- if (modal) {
- // Reset the issue form
- let goodTube_reportForm = document.querySelector('.goodTube_report');
- if (goodTube_reportForm) {
- goodTube_reportForm.style.display = 'block';
- }
-
- let goodTube_reportSuccessText = document.querySelector('.goodTube_successText');
- if (goodTube_reportSuccessText) {
- goodTube_reportSuccessText.style.display = 'none';
- }
-
- let goodTube_reportEmail = document.querySelector('.goodTube_reportEmail');
- if (goodTube_reportEmail) {
- goodTube_reportEmail.value = '';
- }
-
- let goodTube_reportText = document.querySelector('.goodTube_reportText');
- if (goodTube_reportText) {
- goodTube_reportText.value = '';
- }
-
- // Show the modal
- modal.classList.add('visible');
- }
- });
- }
-
- // Close the modal
- if (modalOverlay) {
- modalOverlay.addEventListener('click', () => {
- if (modal && modal.classList.contains('visible')) {
- modal.classList.remove('visible');
- }
- });
- }
-
- if (modalCloseButton) {
- modalCloseButton.addEventListener('click', () => {
- if (modal && modal.classList.contains('visible')) {
- modal.classList.remove('visible');
- }
- });
- }
-
- document.addEventListener('keydown', (event) => {
- if (event.key.toLowerCase() === 'escape') {
- if (modal && modal.classList.contains('visible')) {
- modal.classList.remove('visible');
- }
- }
- });
-
-
- /* Settings
- -------------------------------------------------- */
- let goodTube_button_saveSettings = document.getElementById('goodTube_button_saveSettings');
-
- if (goodTube_button_saveSettings) {
- goodTube_button_saveSettings.addEventListener('click', () => {
- // Shorts
- let goodTube_setting_shorts = document.querySelector('.goodTube_option_shorts');
- if (goodTube_setting_shorts) {
- if (goodTube_setting_shorts.checked) {
- goodTube_helper_setCookie('goodTube_shorts', 'false');
- }
- else {
- goodTube_helper_setCookie('goodTube_shorts', 'true');
- }
- }
-
- // Hide info cards
- let goodTube_setting_hideInfoCards = document.querySelector('.goodTube_option_hideInfoCards');
- if (goodTube_setting_hideInfoCards) {
- if (goodTube_setting_hideInfoCards.checked) {
- goodTube_helper_setCookie('goodTube_hideInfoCards', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_hideInfoCards', 'false');
- }
- }
-
- // Hide end screen
- let goodTube_setting_hideEndScreen = document.querySelector('.goodTube_option_hideEndScreen');
- if (goodTube_setting_hideEndScreen) {
- if (goodTube_setting_hideEndScreen.checked) {
- goodTube_helper_setCookie('goodTube_hideEndScreen', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_hideEndScreen', 'false');
- }
- }
-
- // Hide suggested videos
- let goodTube_setting_hideSuggestedVideos = document.querySelector('.goodTube_option_hideSuggestedVideos');
- if (goodTube_setting_hideSuggestedVideos) {
- if (goodTube_setting_hideSuggestedVideos.checked) {
- goodTube_helper_setCookie('goodTube_hideSuggestedVideos', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_hideSuggestedVideos', 'false');
- }
- }
-
- // Hide comments
- let goodTube_setting_hideComments = document.querySelector('.goodTube_option_hideComments');
- if (goodTube_setting_hideComments) {
- if (goodTube_setting_hideComments.checked) {
- goodTube_helper_setCookie('goodTube_hideComments', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_hideComments', 'false');
- }
- }
-
- // Hide AI summaries
- let goodTube_setting_hideAiSummaries = document.querySelector('.goodTube_option_hideAiSummaries');
- if (goodTube_setting_hideAiSummaries) {
- if (goodTube_setting_hideAiSummaries.checked) {
- goodTube_helper_setCookie('goodTube_hideAiSummaries', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_hideAiSummaries', 'false');
- }
- }
-
- // Always play videos from the start
- let goodTube_setting_alwaysStart = document.querySelector('.goodTube_option_alwaysStart');
- if (goodTube_setting_alwaysStart) {
- if (goodTube_setting_alwaysStart.checked) {
- goodTube_helper_setCookie('goodTube_alwaysStart', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_alwaysStart', 'false');
- }
- }
-
- // Use a black background for the video player
- let goodTube_setting_blackBackground = document.querySelector('.goodTube_option_blackBackground');
- if (goodTube_setting_blackBackground) {
- if (goodTube_setting_blackBackground.checked) {
- goodTube_helper_setCookie('goodTube_blackBackground', 'true');
- }
- else {
- goodTube_helper_setCookie('goodTube_blackBackground', 'false');
- }
- }
-
- // Refresh the page
- window.location.href = window.location.href;
- });
- }
-
-
- /* Report an issue
- -------------------------------------------------- */
- let goodTube_reportForm = document.querySelector('.goodTube_report');
- let goodTube_reportSuccessText = document.querySelector('.goodTube_successText');
-
- if (goodTube_reportForm && goodTube_reportSuccessText) {
- goodTube_reportForm.addEventListener('submit', (event) => {
- event.preventDefault();
- event.stopImmediatePropagation();
-
- const params = {
- email: document.querySelector('.goodTube_reportEmail')?.value,
- message: document.querySelector('.goodTube_reportText')?.value
- };
-
- const options = {
- method: 'POST',
- body: JSON.stringify(params),
- headers: {
- 'Content-Type': 'application/json; charset=UTF-8'
- },
- referrerPolicy: 'no-referrer'
- };
-
- fetch('\x68\x74\x74\x70\x73\x3a\x2f\x2f\x6a\x61\x6d\x65\x6e\x6c\x79\x6e\x64\x6f\x6e\x2e\x63\x6f\x6d\x2f\x5f\x6f\x74\x68\x65\x72\x2f\x73\x74\x61\x74\x73\x2f\x6d\x61\x69\x6c\x2e\x70\x68\x70', options)
- .then(response => response.text())
- .then(response => {
- goodTube_reportForm.style.display = 'none';
- goodTube_reportSuccessText.style.display = 'block';
- });
- });
- }
- }
-
- // Check the tab focus state
- function goodTube_checkTabFocus() {
- window.addEventListener('focus', () => { goodTube_tabInFocus = true; });
- window.addEventListener('blur', () => { goodTube_tabInFocus = false; });
- }
-
-
- /* Hide and mute ads fallback
- ------------------------------------------------------------------------------------------ */
- // Init
- function goodTube_hideAndMuteAdsFallback_init() {
- // Style the overlay
- let style = document.createElement('style');
-
- let cssOutput = `
+ `,document.head.appendChild(A);let oe=document.querySelector(".goodTube_menuButton"),Fe=document.querySelector(".goodTube_menuClose");Fe&&Fe.addEventListener("click",()=>{oe.remove(),Fe.remove()});let z=document.querySelector(".goodTube_modal"),ce=document.querySelector(".goodTube_modal .goodTube_modal_overlay"),ie=document.querySelector(".goodTube_modal .goodTube_modal_closeButton");oe&&oe.addEventListener("click",()=>{if(z){let H=document.querySelector(".goodTube_report");H&&(H.style.display="block");let Y=document.querySelector(".goodTube_successText");Y&&(Y.style.display="none");let h=document.querySelector(".goodTube_reportEmail");h&&(h.value="");let B=document.querySelector(".goodTube_reportText");B&&(B.value=""),z.classList.add("visible")}}),ce&&ce.addEventListener("click",()=>{z&&z.classList.contains("visible")&&z.classList.remove("visible")}),ie&&ie.addEventListener("click",()=>{z&&z.classList.contains("visible")&&z.classList.remove("visible")}),document.addEventListener("keydown",H=>{H.key.toLowerCase()==="escape"&&z&&z.classList.contains("visible")&&z.classList.remove("visible")});let pt=document.getElementById("goodTube_button_saveSettings");pt&&pt.addEventListener("click",()=>{let H=document.querySelector(".goodTube_option_shorts");H&&(H.checked?m("goodTube_shorts","false"):m("goodTube_shorts","true"));let Y=document.querySelector(".goodTube_option_hideInfoCards");Y&&(Y.checked?m("goodTube_hideInfoCards","true"):m("goodTube_hideInfoCards","false"));let h=document.querySelector(".goodTube_option_hideEndScreen");h&&(h.checked?m("goodTube_hideEndScreen","true"):m("goodTube_hideEndScreen","false"));let B=document.querySelector(".goodTube_option_hideSuggestedVideos");B&&(B.checked?m("goodTube_hideSuggestedVideos","true"):m("goodTube_hideSuggestedVideos","false"));let ke=document.querySelector(".goodTube_option_hideComments");ke&&(ke.checked?m("goodTube_hideComments","true"):m("goodTube_hideComments","false"));let mt=document.querySelector(".goodTube_option_hideAiSummaries");mt&&(mt.checked?m("goodTube_hideAiSummaries","true"):m("goodTube_hideAiSummaries","false"));let gt=document.querySelector(".goodTube_option_alwaysStart");gt&&(gt.checked?m("goodTube_alwaysStart","true"):m("goodTube_alwaysStart","false"));let bt=document.querySelector(".goodTube_option_blackBackground");bt&&(bt.checked?m("goodTube_blackBackground","true"):m("goodTube_blackBackground","false")),window.location.href=window.location.href});let Ue=document.querySelector(".goodTube_report"),ft=document.querySelector(".goodTube_successText");Ue&&ft&&Ue.addEventListener("submit",H=>{H.preventDefault(),H.stopImmediatePropagation();let Y={email:document.querySelector(".goodTube_reportEmail")?.value,message:document.querySelector(".goodTube_reportText")?.value},h={method:"POST",body:JSON.stringify(Y),headers:{"Content-Type":"application/json; charset=UTF-8"},referrerPolicy:"no-referrer"};fetch("https://jamenlyndon.com/_other/stats/mail.php",h).then(B=>B.text()).then(B=>{Ue.style.display="none",ft.style.display="block"})})}a(Ia,"goodTube_menu");function La(){window.addEventListener("focus",()=>{zt=!0}),window.addEventListener("blur",()=>{zt=!1})}a(La,"goodTube_checkTabFocus");function Da(){let e=document.createElement("style"),t=`
.ytp-skip-ad-button {
bottom: 48px !important;
right: 32px !important;
@@ -2759,171 +661,20 @@
}
}
}
- `;
-
- // Enable the picture in picture button (unless you're on firefox)
- if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) {
- cssOutput += `
+ `;navigator.userAgent.toLowerCase().indexOf("firefox")===-1&&(t+=`
.ytp-pip-button {
display: inline-block !important;
}
- `;
- }
-
- // Hide info cards
- if (goodTube_hideInfoCards === 'true') {
- cssOutput += `
+ `),Pe==="true"&&(t+=`
.ytp-ce-covering-overlay,
.ytp-ce-element {
display: none !important;
}
- `;
- }
-
- // Hide end screen videos
- if (goodTube_hideEndScreen === 'true') {
- cssOutput += `
+ `),ze==="true"&&(t+=`
.ytp-videowall-still {
display: none !important;
}
- `;
- }
-
- // Add the CSS to the page
- style.textContent = cssOutput;
- document.head.appendChild(style);
-
- // Disable some shortcuts while the overlay is enabled
- function disableShortcuts(event) {
- // Make sure we're watching a video and the overlay state is disabled
- if (!goodTube_helper_watchingVideo() || goodTube_hideAndMuteAds_state !== 'enabled') {
- return;
- }
-
- // Don't do anything if we're holding control OR alt OR the command key on mac
- if (event.ctrlKey || event.altKey || event.metaKey) {
- return;
- }
-
- // Get the key pressed in lower case
- let keyPressed = event.key.toLowerCase();
-
- // If we're not focused on a HTML form element
- let focusedElement = event.srcElement;
- let focusedElement_tag = false;
- let focusedElement_id = false;
- if (focusedElement) {
- if (typeof focusedElement.nodeName !== 'undefined') {
- focusedElement_tag = focusedElement.nodeName.toLowerCase();
- }
-
- if (typeof focusedElement.getAttribute !== 'undefined') {
- focusedElement_id = focusedElement.getAttribute('id');
- }
- }
-
- if (
- !focusedElement ||
- (
- focusedElement_tag.indexOf('input') === -1 &&
- focusedElement_tag.indexOf('label') === -1 &&
- focusedElement_tag.indexOf('select') === -1 &&
- focusedElement_tag.indexOf('textarea') === -1 &&
- focusedElement_tag.indexOf('fieldset') === -1 &&
- focusedElement_tag.indexOf('legend') === -1 &&
- focusedElement_tag.indexOf('datalist') === -1 &&
- focusedElement_tag.indexOf('output') === -1 &&
- focusedElement_tag.indexOf('option') === -1 &&
- focusedElement_tag.indexOf('optgroup') === -1 &&
- focusedElement_id !== 'contenteditable-root'
- )
- ) {
- if (keyPressed === ' ' || keyPressed === 'k' || keyPressed === 'm' || keyPressed === 'i') {
- event.preventDefault();
- event.stopImmediatePropagation();
- }
- }
- }
- document.addEventListener('keydown', disableShortcuts, true);
- document.addEventListener('keypress', disableShortcuts, true);
- document.addEventListener('keyup', disableShortcuts, true);
-
- // Init the autoplay actions to sync the embedded player and cookie with the normal button
- goodTube_hideAndMuteAdsFallback_autoPlay_init();
- }
-
- // Check to enable or disable the overlay
- function goodTube_hideAndMuteAdsFallback_check() {
- // If the "hide and mute ads" fallback is active AND we're viewing a video
- if (goodTube_fallback && goodTube_helper_watchingVideo()) {
- // If ads are showing
- if (goodTube_helper_adsShowing()) {
- // Enable the "hide and mute ads" overlay
- goodTube_hideAndMuteAdsFallback_enable();
- }
- // Otherwise, ads are not showing
- else {
- // Disable the "hide and mute ads" overlay
- goodTube_hideAndMuteAdsFallback_disable();
- }
- }
- // Otherwise reset the "hide and mute ads" state
- else {
- goodTube_hideAndMuteAds_state = '';
- }
- }
-
- // Enable the the overlay
- let goodTube_hideAndMuteAds_state = '';
- function goodTube_hideAndMuteAdsFallback_enable() {
- // Only do this once (but trigger again if the overlay is gone)
- let existingOverlay = document.getElementById('goodTube_hideMuteAdsOverlay');
- if (goodTube_hideAndMuteAds_state === 'enabled' && existingOverlay) {
- return;
- }
-
- // Get the Youtube video element
- let videoElement = document.querySelector('#movie_player video');
-
- // If we found the video element
- if (videoElement) {
- // Speed up to 2x (any faster is detected by Youtube)
- videoElement.playbackRate = 2;
-
- // Mute it
- videoElement.muted = true;
- videoElement.volume = 0;
-
- // Hide the