Greasy Fork is available in English.

Yeah! for Twitter

Adds Yeah! button to Twitter, essentially a public Like

// ==UserScript==
// @name         Yeah! for Twitter
// @namespace    http://tampermonkey.net/
// @version      1.0.8
// @description  Adds Yeah! button to Twitter, essentially a public Like
// @author       dimden.dev
// @match        https://x.com/*
// @match        https://twitter.com/*
// @icon         https://dimden.dev/images/yeah_logo.png
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// ==/UserScript==

// fetch polyfill
function GM_fetch(url, options = {}) {
    return new Promise((resolve, reject) => {
        const method = options.method || 'GET';
        const headers = options.headers || {};
        const body = options.body || null;

        GM_xmlhttpRequest({
            method,
            url,
            headers,
            data: body,
            onload(response) {
                const responseBody = response.responseText;
                const status = response.status;
                const statusText = response.statusText;
                const responseHeaders = parseHeaders(response.responseHeaders);

                resolve(new Response(responseBody, {
                    status,
                    statusText,
                    headers: responseHeaders
                }));
            },
            onerror(error) {
                reject(new Error('Network request failed'));
            },
            ontimeout() {
                reject(new Error('Network request timed out'));
            }
        });
    });
}

function parseHeaders(headersString) {
    const headers = new Headers();
    const lines = headersString.trim().split(/[\r\n]+/);
    lines.forEach(line => {
        const parts = line.split(': ');
        const header = parts.shift();
        const value = parts.join(': ');
        headers.append(header, value);
    });
    return headers;
}

class Response {
    constructor(body, options) {
        this.body = body;
        this.status = options.status;
        this.statusText = options.statusText;
        this.headers = options.headers;
    }

    text() {
        return Promise.resolve(this.body);
    }

    json() {
        return Promise.resolve(JSON.parse(this.body));
    }
}

// chrome.storage.local polyfill
window.chrome = window.chrome || {};
chrome.runtime = chrome.runtime || {id: 'userscript'};
chrome.storage = chrome.storage || {};
chrome.storage.local = {
    storageKey: 'chromeStorage',

    _getStorageObject: function () {
        const storage = localStorage.getItem(this.storageKey);
        return storage ? JSON.parse(storage) : {};
    },

    _setStorageObject: function (obj) {
        localStorage.setItem(this.storageKey, JSON.stringify(obj));
    },

    get: function (keys, callback) {
        const storageObj = this._getStorageObject();
        const result = {};

        if (typeof keys === 'string') {
            result[keys] = storageObj[keys];
        } else if (Array.isArray(keys)) {
            keys.forEach(key => {
                result[key] = storageObj[key];
            });
        } else if (typeof keys === 'object') {
            Object.keys(keys).forEach(key => {
                result[key] = storageObj[key] !== undefined ? storageObj[key] : keys[key];
            });
        } else {
            Object.assign(result, storageObj);
        }

        callback(result);
    },

    set: function (items, callback) {
        const storageObj = this._getStorageObject();

        Object.keys(items).forEach(key => {
            storageObj[key] = items[key];
        });

        this._setStorageObject(storageObj);

        if (callback) callback();
    },

    remove: function (keys, callback) {
        const storageObj = this._getStorageObject();

        if (typeof keys === 'string') {
            delete storageObj[keys];
        } else if (Array.isArray(keys)) {
            keys.forEach(key => {
                delete storageObj[key];
            });
        }

        this._setStorageObject(storageObj);

        if (callback) callback();
    },

    clear: function (callback) {
        localStorage.removeItem(this.storageKey);
        if (callback) callback();
    }
};

if(this.GM_registerMenuCommand) {
    GM_registerMenuCommand("Don't like tweet on Yeah", function () {
        chrome.storage.local.set({
            settings: {
                dontLike: true
            }
        });
    });
    
    GM_registerMenuCommand("Like tweet on Yeah", function () {
        chrome.storage.local.set({
            settings: {
                dontLike: false
            }
        });
    });
    
    GM_registerMenuCommand("Clear account tokens", function () {
        chrome.storage.local.remove(['yeahToken', 'yeahTokens']);
    });
    
    GM_registerMenuCommand("Reset popup settings", function () {
        chrome.storage.local.remove(['ignorePopup']);
    });
}

let fontstyle = document.createElement('style');
fontstyle.innerHTML = `
@font-face {
    font-family: 'RosettaIcons2';
    src: url(data:application/x-font-woff;charset=utf-8;base64,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);
}
`;
document.head.appendChild(fontstyle);

let YEAH_images = {};
YEAH_images['yeah_on32.png'] = 'data:image/png;charset=utf-8;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAABemlDQ1BJQ0MgUHJvZmlsZQAAKM+Vkc8rRFEUxz9jRiNGFMXC4iWsEKMGG4uRX4XFzCi/Nm+eNzNqfrzem0myVbZTlNj4teAvYKuslSJSsrYmNug5z1MjNQv3ds/53O+953TPuVARS2sZy9cDmWzejIyFldm5ecX/hI8mauknqGqWMRUdjVF2vN3icfx1l5OL/42aJd3SwFMlPKQZZl54XHhyJW84vCXcqKXUJeET4U5THih84+hxl58cTrr84bAZiwxLbfXCSvIXx3+xljIzwlI5bZl0Qft5j1NJQM/ORMW3ymrBIsIYYRQmGGGYEL0Mig3RRZBu2VEmvuc7fpqcxGpiDVYxWSZJijydohYkuy4+IbouM82q0/+/fbUSfUE3eyAMlY+2/dIO/k34LNr2+4Ftfx6C9wHOs6X43D4MvIpeLGlte1C3DqcXJS2+DWcb0HxvqKb6LXllVSQS8HwMtXPQcAXVC27Pfs45uoPYmnzVJezsQofcr1v8AtdeaBm3tpVPAAAACXBIWXMAAC4hAAAuIQEHW/z/AAAGCUlEQVRYR+1Wa2xURRT+5j52t9stfbcEWt4FawkPhViwaIiiISBP5RGVPyQIEQ1R//hI/OUjaAQ1xBCNQU1I1VQhvqHIUwSNhBBqrQVBSkD62LbSbfc+xzNz7y7b7Ta0iT/5sidz55wzc745c2ZmcQtDwraicEUdKyr/XC0EqnRf+7+A+e1A7FJy1lW5q/JzsCI7hEpdQ4QD3LTQHuvDL23d2F07B/uF66noMXZHQQ2Zh4+MBFYfx4qyEmwtysfE7DCgquToe3IKY1nA9RjQFsW+5st4+rv70dTjdrGIkjdsEgMIPP4rXhg7Cq8U5FNgBXDFlGnTCjJCBJHWDnScv4yVdTU4fLrzOJuRP3dYJPoRWPMzNk4Yg/fycr2VipkGMPQhbcJIH23tiDacRw1lolEahwFao4f532JyaQneyMoGDBswbdW17cg1U37fEM+mmbaT3WZQBkwHyMlFwbjR2OFPNSwkCZSPxHPhCCJiQhHEsLQPJuPhsaYdOWQInS/CblqhlyLWnbdZdtY54WtTFiIjMH/VCSzypxsyJIEJH0cKaOUPuZRSb4VUZH3Gn6vydxk9RqxZBvVF2DtjPY1bio9EY2a8JaFjVKg5OXhMzjoMSAKV5T1Vio6RMs1+IMv1bKbDFalLsdkuKJy0sRR/qDpmY1t5lrANFTKIHsQYmsNLcSKQFZQXjmVlh5I60ZJYVlgSsOyQZ/PtlMGiBVOv0vkZOiQBzhAWK0gGksHY+mXHil83HXOhKWy+XvgZFnt+xfGibaZjz0zoBXmyqbpOuRwG5ClbdIwtySvie1VN6gTkKeMUjEmKaSCrOKbpNtvEtastwcqDDxqdvuqmkATm17OKwlJ+RgsglDj89LtEn9nUL+x3GQg7ge6Ac/RZQv0RCbtl4FjdNMzb3IBH6eqeSSpO9XKlq7ts5yfVl3s9r/5ITr34NxwJhDDP78KKszXhbKXRcpx91C31tB5oUH2uVrAwGu/arWjuI1JJbPr62DOzSlEbjvCWYMArVIu2tfs6W/5qOd8j/dKQTGI8znYkLhYhcYu/+OP3ens4ok92wTbRWd9ORb+VM7akrgoLGpr/XWTYfLGV9Edb/T/8IwR4LqfQJhES4lIEPcTpRcmM1OTivpPYowawlJRyi81e1hbrYm+3X9H2Xmi2WxFW1MoKPimvmK8P57rraMsY+cnVOyY2HqjGzi0Xs6rC4b7TigJZUWIiw8DaN8tQK/rp6Eegep9WFBzh7Gcan5EwiEJ0KDOui17SKYqGEIl8B2jyJmq22w6aj1bjgPB/8q/CiWqg4w96yLQEOXq0Vu4Yhy9ENx39CAjMrmfFehb/kG62xanWxKecVLQu2u0+9a5cg09RAu5UctDp5XTC2RhVXILNdEK87aUB3V34NNqBU4IUaRTHRVSN3b7rh6W/i0VlxqxD7CnKxDs3qqQ/7Di+Hkm1ES7gf2s6lAQxkZmU4yzhUo1QBiVEQIf6fTGs/WYOageZnt75Ft4cNyh95CwqWbYpYpiw1QgP2SoccksWnUGBek0gRpJo4+SftJPYImoAeSLOoATUIB6wiW7iVKReuULo1uO9VBsUQI8RAxEoVdJJpEqP7y8wKAFXw1zxzMoV06qSIvokts1GG22h1j4Lh4lIJ0mbL+JbBk6Khesp9g4ac8F29ZMiTsYaKN4WGBMcb57VwsghDgMgBrk0cfya8mzrevetu38qK6bDIa5mq8s4N5WWdZBqwTuGQhw8kRcavxuOmsXpyeKcx47WXKB/lYMQKHgfy4Kl+JLd5FlxYoi2N2CS8zKSd/+0r8ZUxrMunSEyXimKPTex+vxCfCb7aci4BVzHPbTdyXRnFNoOR0NBZBSme6M8cFdVKcVIiNgOumEH3eqMBkfFPBqXOXCKCJKOjnvlIB+G5TIKrCUI0BWNuO29C5kwgMCI10JlrsJGOg7rcVzWPag4JJwZXGXT/KESClfF37TjFDRK0k7/Ey4ynnXWNw/AgBqYuWN64GJeUwkLeNf8TcDcuGtt7Xm3dcOmDUn/OXurg6fsEzmUJFQoU+KNy5t6PMstpAP4D763SJ9bdwflAAAAAElFTkSuQmCC'
YEAH_images['yeah_off32.png'] = 'data:image/png;charset=utf-8;base64,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'
YEAH_images['loading.svg'] = 'data:image/svg+xml;charset=utf-8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiBzdHlsZT0ibWFyZ2luOiBhdXRvOyBiYWNrZ3JvdW5kOiBub25lOyBkaXNwbGF5OiBibG9jazsgc2hhcGUtcmVuZGVyaW5nOiBhdXRvOyBhbmltYXRpb24tcGxheS1zdGF0ZTogcnVubmluZzsgYW5pbWF0aW9uLWRlbGF5OiAwczsiIHdpZHRoPSIyMDBweCIgaGVpZ2h0PSIyMDBweCIgdmlld0JveD0iMCAwIDEwMCAxMDAiIHByZXNlcnZlQXNwZWN0UmF0aW89InhNaWRZTWlkIj4KPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjY2FjYWNhIiBzdHJva2Utd2lkdGg9IjgiIHI9IjM1IiBzdHJva2UtZGFzaGFycmF5PSIxNjQuOTMzNjE0MzEzNDY0MTUgNTYuOTc3ODcxNDM3ODIxMzgiIHN0eWxlPSJhbmltYXRpb24tcGxheS1zdGF0ZTogcnVubmluZzsgYW5pbWF0aW9uLWRlbGF5OiAwczsiPgogIDxhbmltYXRlVHJhbnNmb3JtIGF0dHJpYnV0ZU5hbWU9InRyYW5zZm9ybSIgdHlwZT0icm90YXRlIiByZXBlYXRDb3VudD0iaW5kZWZpbml0ZSIgZHVyPSIwLjgxOTY3MjEzMTE0NzU0MXMiIHZhbHVlcz0iMCA1MCA1MDszNjAgNTAgNTAiIGtleVRpbWVzPSIwOzEiIHN0eWxlPSJhbmltYXRpb24tcGxheS1zdGF0ZTogcnVubmluZzsgYW5pbWF0aW9uLWRlbGF5OiAwczsiPjwvYW5pbWF0ZVRyYW5zZm9ybT4KPC9jaXJjbGU+CjwhLS0gW2xkaW9dIGdlbmVyYXRlZCBieSBodHRwczovL2xvYWRpbmcuaW8vIC0tPjwvc3ZnPg=='

// scripts/purify.min.js
/*! @license DOMPurify 3.1.3 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.3/LICENSE */
!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,n){return e.apply(t,n)}),s||(s=function(e,t){return new e(...t)});const u=S(Array.prototype.forEach),m=S(Array.prototype.pop),p=S(Array.prototype.push),f=S(String.prototype.toLowerCase),d=S(String.prototype.toString),h=S(String.prototype.match),g=S(String.prototype.replace),_=S(String.prototype.indexOf),T=S(String.prototype.trim),y=S(Object.prototype.hasOwnProperty),E=S(RegExp.prototype.test),A=(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;const b=S(Number.isNaN);function S(e){return function(t){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]:f;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 w(e){for(let t=0;t<e.length;t++){y(e,t)||(e[t]=null)}return e}function C(t){const n=l(null);for(const[o,r]of e(t)){y(t,o)&&(Array.isArray(r)?n[o]=w(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 S(n.get);if("function"==typeof n.value)return S(n.value)}e=o(e)}return function(){return null}}const L=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","section","select","shadow","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"]),D=i(["svg","a","altglyph","altglyphdef","altglyphitem","animatecolor","animatemotion","animatetransform","circle","clippath","defs","desc","ellipse","filter","font","g","glyph","glyphref","hkern","image","line","lineargradient","marker","mask","metadata","mpath","path","pattern","polygon","polyline","radialgradient","rect","stop","style","switch","symbol","text","textpath","title","tref","tspan","view","vkern"]),O=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"]),x=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"]),k=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"]),I=i(["#text"]),U=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","face","for","headers","height","hidden","high","href","hreflang","id","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","pattern","placeholder","playsinline","poster","preload","pubdate","radiogroup","readonly","rel","required","rev","reversed","role","rows","rowspan","spellcheck","scope","selected","shape","size","sizes","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","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","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","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","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","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","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"]),z=a(/\{\{[\w\W]*|[\w\W]*\}\}/gm),B=a(/<%[\w\W]*|[\w\W]*%>/gm),W=a(/\${[\w\W]*}/gm),G=a(/^data-[\-\w.\u00B7-\uFFFF]/),Y=a(/^aria-[\-\w]+$/),j=a(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),X=a(/^(?:\w+script|data):/i),q=a(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),$=a(/^html$/i),K=a(/^[a-z][.\w]*(-[.\w]+)+$/i);var V=Object.freeze({__proto__:null,MUSTACHE_EXPR:z,ERB_EXPR:B,TMPLIT_EXPR:W,DATA_ATTR:G,ARIA_ATTR:Y,IS_ALLOWED_URI:j,IS_SCRIPT_OR_DATA:X,ATTR_WHITESPACE:q,DOCTYPE_NAME:$,CUSTOM_ELEMENT:K});const Z=1,J=3,Q=7,ee=8,te=9,ne=function(){return"undefined"==typeof window?null:window},oe=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}};var re=function t(){let n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:ne();const o=e=>t(e);if(o.version="3.1.3",o.removed=[],!n||!n.document||n.document.nodeType!==te)return o.isSupported=!1,o;let{document:r}=n;const a=r,c=a.currentScript,{DocumentFragment:s,HTMLTemplateElement:N,Node:S,Element:w,NodeFilter:z,NamedNodeMap:B=n.NamedNodeMap||n.MozNamedAttrMap,HTMLFormElement:W,DOMParser:G,trustedTypes:Y}=n,X=w.prototype,q=v(X,"cloneNode"),K=v(X,"nextSibling"),re=v(X,"childNodes"),ie=v(X,"parentNode");if("function"==typeof N){const e=r.createElement("template");e.content&&e.content.ownerDocument&&(r=e.content.ownerDocument)}let ae,le="";const{implementation:ce,createNodeIterator:se,createDocumentFragment:ue,getElementsByTagName:me}=r,{importNode:pe}=a;let fe={};o.isSupported="function"==typeof e&&"function"==typeof ie&&ce&&void 0!==ce.createHTMLDocument;const{MUSTACHE_EXPR:de,ERB_EXPR:he,TMPLIT_EXPR:ge,DATA_ATTR:_e,ARIA_ATTR:Te,IS_SCRIPT_OR_DATA:ye,ATTR_WHITESPACE:Ee,CUSTOM_ELEMENT:Ae}=V;let{IS_ALLOWED_URI:Ne}=V,be=null;const Se=R({},[...L,...D,...O,...k,...I]);let Re=null;const we=R({},[...U,...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,Le=null,De=!0,Oe=!0,xe=!1,ke=!0,Me=!1,Ie=!0,Ue=!1,Pe=!1,Fe=!1,He=!1,ze=!1,Be=!1,We=!0,Ge=!1;const Ye="user-content-";let je=!0,Xe=!1,qe={},$e=null;const Ke=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 Ve=null;const Ze=R({},["audio","video","img","source","image","track"]);let Je=null;const Qe=R({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),et="http://www.w3.org/1998/Math/MathML",tt="http://www.w3.org/2000/svg",nt="http://www.w3.org/1999/xhtml";let ot=nt,rt=!1,it=null;const at=R({},[et,tt,nt],d);let lt=null;const ct=["application/xhtml+xml","text/html"],st="text/html";let ut=null,mt=null;const pt=255,ft=r.createElement("form"),dt=function(e){return e instanceof RegExp||e instanceof Function},ht=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};if(!mt||mt!==e){if(e&&"object"==typeof e||(e={}),e=C(e),lt=-1===ct.indexOf(e.PARSER_MEDIA_TYPE)?st:e.PARSER_MEDIA_TYPE,ut="application/xhtml+xml"===lt?d:f,be=y(e,"ALLOWED_TAGS")?R({},e.ALLOWED_TAGS,ut):Se,Re=y(e,"ALLOWED_ATTR")?R({},e.ALLOWED_ATTR,ut):we,it=y(e,"ALLOWED_NAMESPACES")?R({},e.ALLOWED_NAMESPACES,d):at,Je=y(e,"ADD_URI_SAFE_ATTR")?R(C(Qe),e.ADD_URI_SAFE_ATTR,ut):Qe,Ve=y(e,"ADD_DATA_URI_TAGS")?R(C(Ze),e.ADD_DATA_URI_TAGS,ut):Ze,$e=y(e,"FORBID_CONTENTS")?R({},e.FORBID_CONTENTS,ut):Ke,ve=y(e,"FORBID_TAGS")?R({},e.FORBID_TAGS,ut):{},Le=y(e,"FORBID_ATTR")?R({},e.FORBID_ATTR,ut):{},qe=!!y(e,"USE_PROFILES")&&e.USE_PROFILES,De=!1!==e.ALLOW_ARIA_ATTR,Oe=!1!==e.ALLOW_DATA_ATTR,xe=e.ALLOW_UNKNOWN_PROTOCOLS||!1,ke=!1!==e.ALLOW_SELF_CLOSE_IN_ATTR,Me=e.SAFE_FOR_TEMPLATES||!1,Ie=!1!==e.SAFE_FOR_XML,Ue=e.WHOLE_DOCUMENT||!1,He=e.RETURN_DOM||!1,ze=e.RETURN_DOM_FRAGMENT||!1,Be=e.RETURN_TRUSTED_TYPE||!1,Fe=e.FORCE_BODY||!1,We=!1!==e.SANITIZE_DOM,Ge=e.SANITIZE_NAMED_PROPS||!1,je=!1!==e.KEEP_CONTENT,Xe=e.IN_PLACE||!1,Ne=e.ALLOWED_URI_REGEXP||j,ot=e.NAMESPACE||nt,Ce=e.CUSTOM_ELEMENT_HANDLING||{},e.CUSTOM_ELEMENT_HANDLING&&dt(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(Ce.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&dt(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),Me&&(Oe=!1),ze&&(He=!0),qe&&(be=R({},I),Re=[],!0===qe.html&&(R(be,L),R(Re,U)),!0===qe.svg&&(R(be,D),R(Re,P),R(Re,H)),!0===qe.svgFilters&&(R(be,O),R(Re,P),R(Re,H)),!0===qe.mathMl&&(R(be,k),R(Re,F),R(Re,H))),e.ADD_TAGS&&(be===Se&&(be=C(be)),R(be,e.ADD_TAGS,ut)),e.ADD_ATTR&&(Re===we&&(Re=C(Re)),R(Re,e.ADD_ATTR,ut)),e.ADD_URI_SAFE_ATTR&&R(Je,e.ADD_URI_SAFE_ATTR,ut),e.FORBID_CONTENTS&&($e===Ke&&($e=C($e)),R($e,e.FORBID_CONTENTS,ut)),je&&(be["#text"]=!0),Ue&&R(be,["html","head","body"]),be.table&&(R(be,["tbody"]),delete ve.tbody),e.TRUSTED_TYPES_POLICY){if("function"!=typeof e.TRUSTED_TYPES_POLICY.createHTML)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');if("function"!=typeof e.TRUSTED_TYPES_POLICY.createScriptURL)throw A('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');ae=e.TRUSTED_TYPES_POLICY,le=ae.createHTML("")}else void 0===ae&&(ae=oe(Y,c)),null!==ae&&"string"==typeof le&&(le=ae.createHTML(""));i&&i(e),mt=e}},gt=R({},["mi","mo","mn","ms","mtext"]),_t=R({},["foreignobject","annotation-xml"]),Tt=R({},["title","style","font","a","script"]),yt=R({},[...D,...O,...x]),Et=R({},[...k,...M]),At=function(e){let t=ie(e);t&&t.tagName||(t={namespaceURI:ot,tagName:"template"});const n=f(e.tagName),o=f(t.tagName);return!!it[e.namespaceURI]&&(e.namespaceURI===tt?t.namespaceURI===nt?"svg"===n:t.namespaceURI===et?"svg"===n&&("annotation-xml"===o||gt[o]):Boolean(yt[n]):e.namespaceURI===et?t.namespaceURI===nt?"math"===n:t.namespaceURI===tt?"math"===n&&_t[o]:Boolean(Et[n]):e.namespaceURI===nt?!(t.namespaceURI===tt&&!_t[o])&&(!(t.namespaceURI===et&&!gt[o])&&(!Et[n]&&(Tt[n]||!yt[n]))):!("application/xhtml+xml"!==lt||!it[e.namespaceURI]))},Nt=function(e){p(o.removed,{element:e});try{e.parentNode.removeChild(e)}catch(t){e.remove()}},bt=function(e,t){try{p(o.removed,{attribute:t.getAttributeNode(e),from:t})}catch(e){p(o.removed,{attribute:null,from:t})}if(t.removeAttribute(e),"is"===e&&!Re[e])if(He||ze)try{Nt(t)}catch(e){}else try{t.setAttribute(e,"")}catch(e){}},St=function(e){let t=null,n=null;if(Fe)e="<remove></remove>"+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===lt&&ot===nt&&(e='<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>'+e+"</body></html>");const o=ae?ae.createHTML(e):e;if(ot===nt)try{t=(new G).parseFromString(o,lt)}catch(e){}if(!t||!t.documentElement){t=ce.createDocument(ot,"template",null);try{t.documentElement.innerHTML=rt?le:o}catch(e){}}const i=t.body||t.documentElement;return e&&n&&i.insertBefore(r.createTextNode(n),i.childNodes[0]||null),ot===nt?me.call(t,Ue?"html":"body")[0]:Ue?t.documentElement:i},Rt=function(e){return se.call(e.ownerDocument||e,e,z.SHOW_ELEMENT|z.SHOW_COMMENT|z.SHOW_TEXT|z.SHOW_PROCESSING_INSTRUCTION|z.SHOW_CDATA_SECTION,null)},wt=function(e){return e instanceof W&&(void 0!==e.__depth&&"number"!=typeof e.__depth||void 0!==e.__removalCount&&"number"!=typeof e.__removalCount||"string"!=typeof e.nodeName||"string"!=typeof e.textContent||"function"!=typeof e.removeChild||!(e.attributes instanceof B)||"function"!=typeof e.removeAttribute||"function"!=typeof e.setAttribute||"string"!=typeof e.namespaceURI||"function"!=typeof e.insertBefore||"function"!=typeof e.hasChildNodes)},Ct=function(e){return"function"==typeof S&&e instanceof S},vt=function(e,t,n){fe[e]&&u(fe[e],(e=>{e.call(o,t,n,mt)}))},Lt=function(e){let t=null;if(vt("beforeSanitizeElements",e,null),wt(e))return Nt(e),!0;const n=ut(e.nodeName);if(vt("uponSanitizeElement",e,{tagName:n,allowedTags:be}),e.hasChildNodes()&&!Ct(e.firstElementChild)&&E(/<[/\w]/g,e.innerHTML)&&E(/<[/\w]/g,e.textContent))return Nt(e),!0;if(e.nodeType===Q)return Nt(e),!0;if(Ie&&e.nodeType===ee&&E(/<[/\w]/g,e.data))return Nt(e),!0;if(!be[n]||ve[n]){if(!ve[n]&&Ot(n)){if(Ce.tagNameCheck instanceof RegExp&&E(Ce.tagNameCheck,n))return!1;if(Ce.tagNameCheck instanceof Function&&Ce.tagNameCheck(n))return!1}if(je&&!$e[n]){const t=ie(e)||e.parentNode,n=re(e)||e.childNodes;if(n&&t){for(let o=n.length-1;o>=0;--o){const r=q(n[o],!0);r.__removalCount=(e.__removalCount||0)+1,t.insertBefore(r,K(e))}}}return Nt(e),!0}return e instanceof w&&!At(e)?(Nt(e),!0):"noscript"!==n&&"noembed"!==n&&"noframes"!==n||!E(/<\/no(script|embed|frames)/i,e.innerHTML)?(Me&&e.nodeType===J&&(t=e.textContent,u([de,he,ge],(e=>{t=g(t,e," ")})),e.textContent!==t&&(p(o.removed,{element:e.cloneNode()}),e.textContent=t)),vt("afterSanitizeElements",e,null),!1):(Nt(e),!0)},Dt=function(e,t,n){if(We&&("id"===t||"name"===t)&&(n in r||n in ft||"__depth"===n||"__removalCount"===n))return!1;if(Oe&&!Le[t]&&E(_e,t));else if(De&&E(Te,t));else if(!Re[t]||Le[t]){if(!(Ot(e)&&(Ce.tagNameCheck instanceof RegExp&&E(Ce.tagNameCheck,e)||Ce.tagNameCheck instanceof Function&&Ce.tagNameCheck(e))&&(Ce.attributeNameCheck instanceof RegExp&&E(Ce.attributeNameCheck,t)||Ce.attributeNameCheck instanceof Function&&Ce.attributeNameCheck(t))||"is"===t&&Ce.allowCustomizedBuiltInElements&&(Ce.tagNameCheck instanceof RegExp&&E(Ce.tagNameCheck,n)||Ce.tagNameCheck instanceof Function&&Ce.tagNameCheck(n))))return!1}else if(Je[t]);else if(E(Ne,g(n,Ee,"")));else if("src"!==t&&"xlink:href"!==t&&"href"!==t||"script"===e||0!==_(n,"data:")||!Ve[e]){if(xe&&!E(ye,g(n,Ee,"")));else if(n)return!1}else;return!0},Ot=function(e){return"annotation-xml"!==e&&h(e,Ae)},xt=function(e){vt("beforeSanitizeAttributes",e,null);const{attributes:t}=e;if(!t)return;const n={attrName:"",attrValue:"",keepAttr:!0,allowedAttributes:Re};let r=t.length;for(;r--;){const i=t[r],{name:a,namespaceURI:l,value:c}=i,s=ut(a);let p="value"===a?c:T(c);if(n.attrName=s,n.attrValue=p,n.keepAttr=!0,n.forceKeepAttr=void 0,vt("uponSanitizeAttribute",e,n),p=n.attrValue,n.forceKeepAttr)continue;if(bt(a,e),!n.keepAttr)continue;if(!ke&&E(/\/>/i,p)){bt(a,e);continue}if(Ie&&E(/((--!?|])>)|<\/(style|title)/i,p)){bt(a,e);continue}Me&&u([de,he,ge],(e=>{p=g(p,e," ")}));const f=ut(e.nodeName);if(Dt(f,s,p)){if(!Ge||"id"!==s&&"name"!==s||(bt(a,e),p=Ye+p),ae&&"object"==typeof Y&&"function"==typeof Y.getAttributeType)if(l);else switch(Y.getAttributeType(f,s)){case"TrustedHTML":p=ae.createHTML(p);break;case"TrustedScriptURL":p=ae.createScriptURL(p)}try{l?e.setAttributeNS(l,a,p):e.setAttribute(a,p),wt(e)?Nt(e):m(o.removed)}catch(e){}}}vt("afterSanitizeAttributes",e,null)},kt=function e(t){let n=null;const o=Rt(t);for(vt("beforeSanitizeShadowDOM",t,null);n=o.nextNode();){if(vt("uponSanitizeShadowNode",n,null),Lt(n))continue;const t=ie(n);n.nodeType===Z&&(t&&t.__depth?n.__depth=(n.__removalCount||0)+t.__depth+1:n.__depth=1),(n.__depth>=pt||n.__depth<0||b(n.__depth))&&Nt(n),n.content instanceof s&&(n.content.__depth=n.__depth,e(n.content)),xt(n)}vt("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(rt=!e,rt&&(e="\x3c!--\x3e"),"string"!=typeof e&&!Ct(e)){if("function"!=typeof e.toString)throw A("toString is not a function");if("string"!=typeof(e=e.toString()))throw A("dirty is not a string, aborting")}if(!o.isSupported)return e;if(Pe||ht(t),o.removed=[],"string"==typeof e&&(Xe=!1),Xe){if(e.nodeName){const t=ut(e.nodeName);if(!be[t]||ve[t])throw A("root node is forbidden and cannot be sanitized in-place")}}else if(e instanceof S)n=St("\x3c!----\x3e"),r=n.ownerDocument.importNode(e,!0),r.nodeType===Z&&"BODY"===r.nodeName||"HTML"===r.nodeName?n=r:n.appendChild(r);else{if(!He&&!Me&&!Ue&&-1===e.indexOf("<"))return ae&&Be?ae.createHTML(e):e;if(n=St(e),!n)return He?null:Be?le:""}n&&Fe&&Nt(n.firstChild);const c=Rt(Xe?e:n);for(;i=c.nextNode();){if(Lt(i))continue;const e=ie(i);i.nodeType===Z&&(e&&e.__depth?i.__depth=(i.__removalCount||0)+e.__depth+1:i.__depth=1),(i.__depth>=pt||i.__depth<0||b(i.__depth))&&Nt(i),i.content instanceof s&&(i.content.__depth=i.__depth,kt(i.content)),xt(i)}if(Xe)return e;if(He){if(ze)for(l=ue.call(n.ownerDocument);n.firstChild;)l.appendChild(n.firstChild);else l=n;return(Re.shadowroot||Re.shadowrootmode)&&(l=pe.call(a,l,!0)),l}let m=Ue?n.outerHTML:n.innerHTML;return Ue&&be["!doctype"]&&n.ownerDocument&&n.ownerDocument.doctype&&n.ownerDocument.doctype.name&&E($,n.ownerDocument.doctype.name)&&(m="<!DOCTYPE "+n.ownerDocument.doctype.name+">\n"+m),Me&&u([de,he,ge],(e=>{m=g(m,e," ")})),ae&&Be?ae.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};ht(e),Pe=!0},o.clearConfig=function(){mt=null,Pe=!1},o.isValidAttribute=function(e,t,n){mt||ht({});const o=ut(e),r=ut(t);return Dt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(fe[e]=fe[e]||[],p(fe[e],t))},o.removeHook=function(e){if(fe[e])return m(fe[e])},o.removeHooks=function(e){fe[e]&&(fe[e]=[])},o.removeAllHooks=function(){fe={}},o}();return re}));
//# sourceMappingURL=purify.min.js.map

// scripts/api.js
const publicToken = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA";
function getCsrf() {
    let csrf = document.cookie.match(/(?:^|;\s*)ct0=([0-9a-f]+)\s*(?:;|$)/);
    return csrf ? csrf[1] : "";
}

function debugLog(...args) {
    if(typeof vars === "object" && vars.developerMode) {
        if(args[0] === 'notifications.get' && !document.querySelector('.notifications-modal') && !location.pathname.startsWith('/notifications')) return; 
        if(vars.extensiveLogging) {
            console.trace(...args);
        } else {
            console.log(...args, new Error().stack.split("\n")[2].trim()); // genius
        }
    }
}

// extract full text and url entities from "note_tweet"
function parseNoteTweet(result) {
    let text, entities;
    if(result.note_tweet.note_tweet_results.result) {
        text = result.note_tweet.note_tweet_results.result.text;
        entities = result.note_tweet.note_tweet_results.result.entity_set;
        if(result.note_tweet.note_tweet_results.result.richtext?.richtext_tags.length) {
            entities.richtext = result.note_tweet.note_tweet_results.result.richtext.richtext_tags // logically, richtext is an entity, right?
        }
    } else {
        text = result.note_tweet.note_tweet_results.text;
        entities = result.note_tweet.note_tweet_results.entity_set;
    }
    return {text, entities};
}


// transform ugly useless twitter api reply to usable legacy tweet
function parseTweet(res) {
    if(typeof res !== "object") return;
    if(res.limitedActionResults) {
        let limitation = res.limitedActionResults.limited_actions.find(l => l.action === "Reply");
        if(limitation) {
            res.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message;
        }
        res = res.tweet;
    }
    if(!res.legacy && res.tweet) res = res.tweet;
    let tweet = res.legacy;
    if(!res.core) return;
    tweet.user = res.core.user_results.result.legacy;
    tweet.user.id_str = tweet.user_id_str;
    if(res.core.user_results.result.is_blue_verified && !res.core.user_results.result.legacy.verified_type) {
        tweet.user.verified = true;
        tweet.user.verified_type = "Blue";
    }
    if(tweet.retweeted_status_result) {
        let result = tweet.retweeted_status_result.result;
        if(result.limitedActionResults && result.tweet && result.tweet.legacy) {
            let limitation = result.limitedActionResults.limited_actions.find(l => l.action === "Reply");
            if(limitation) {
                result.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message;
            }
        }
        if(result.tweet) result = result.tweet;
        if(
            result.quoted_status_result && 
            result.quoted_status_result.result && 
            result.quoted_status_result.result.legacy &&
            result.quoted_status_result.result.core &&
            result.quoted_status_result.result.core.user_results.result.legacy    
        ) {
            result.legacy.quoted_status = result.quoted_status_result.result.legacy;
            if(result.legacy.quoted_status) {
                result.legacy.quoted_status.user = result.quoted_status_result.result.core.user_results.result.legacy;
                result.legacy.quoted_status.user.id_str = result.legacy.quoted_status.user_id_str;
                if(result.quoted_status_result.result.core.user_results.result.is_blue_verified && !result.quoted_status_result.result.core.user_results.result.legacy.verified_type) {
                    result.legacy.quoted_status.user.verified = true;
                    result.legacy.quoted_status.user.verified_type = "Blue";
                }
                tweetStorage[result.legacy.quoted_status.id_str] = result.legacy.quoted_status;
                tweetStorage[result.legacy.quoted_status.id_str].cacheDate = Date.now();
                userStorage[result.legacy.quoted_status.user.id_str] = result.legacy.quoted_status.user;
                userStorage[result.legacy.quoted_status.user.id_str].cacheDate = Date.now();
            } else {
                console.warn("No retweeted quoted status", result);
            }
        } else if(
            result.quoted_status_result &&
            result.quoted_status_result.result &&  
            result.quoted_status_result.result.tweet && 
            result.quoted_status_result.result.tweet.legacy &&
            result.quoted_status_result.result.tweet.core &&
            result.quoted_status_result.result.tweet.core.user_results.result.legacy    
        ) {
            result.legacy.quoted_status = result.quoted_status_result.result.tweet.legacy;
            if(result.legacy.quoted_status) {
                result.legacy.quoted_status.user = result.quoted_status_result.result.tweet.core.user_results.result.legacy;
                result.legacy.quoted_status.user.id_str = result.legacy.quoted_status.user_id_str;
                if(result.quoted_status_result.result.tweet.core.user_results.result.is_blue_verified && !result.core.user_results.result.verified_type) {
                    result.legacy.quoted_status.user.verified = true;
                    result.legacy.quoted_status.user.verified_type = "Blue";
                }
                tweetStorage[result.legacy.quoted_status.id_str] = result.legacy.quoted_status;
                tweetStorage[result.legacy.quoted_status.id_str].cacheDate = Date.now();
                userStorage[result.legacy.quoted_status.user.id_str] = result.legacy.quoted_status.user;
                userStorage[result.legacy.quoted_status.user.id_str].cacheDate = Date.now();
            } else {
                console.warn("No retweeted quoted status", result);
            }
        }
        tweet.retweeted_status = result.legacy;
        if(tweet.retweeted_status && result.core.user_results.result.legacy) {
            tweet.retweeted_status.user = result.core.user_results.result.legacy;
            tweet.retweeted_status.user.id_str = tweet.retweeted_status.user_id_str;
            if(result.core.user_results.result.is_blue_verified && !result.core.user_results.result.legacy.verified_type) {
                tweet.retweeted_status.user.verified = true;
                tweet.retweeted_status.user.verified_type = "Blue";
            }
            tweet.retweeted_status.ext = {};
            if(result.views) {
                tweet.retweeted_status.ext.views = {r: {ok: {count: +result.views.count}}};
            }
            tweet.retweeted_status.res = res;
            if(res.card && res.card.legacy && res.card.legacy.binding_values) {
                tweet.retweeted_status.card = res.card.legacy;
            }
            tweetStorage[tweet.retweeted_status.id_str] = tweet.retweeted_status;
            tweetStorage[tweet.retweeted_status.id_str].cacheDate = Date.now();
            userStorage[tweet.retweeted_status.user.id_str] = tweet.retweeted_status.user;
            userStorage[tweet.retweeted_status.user.id_str].cacheDate = Date.now();
        } else {
            console.warn("No retweeted status", result);
        }
        if(result.note_tweet && result.note_tweet.note_tweet_results) {
            let note = parseNoteTweet(result);
            tweet.retweeted_status.full_text = note.text;
            tweet.retweeted_status.entities = note.entities;
            tweet.retweeted_status.display_text_range = undefined; // no text range for long tweets
        }
    }

    if(res.quoted_status_result) {
        tweet.quoted_status_result = res.quoted_status_result;
    }
    if(res.note_tweet && res.note_tweet.note_tweet_results) {
        let note = parseNoteTweet(res);
        tweet.full_text = note.text;
        tweet.entities = note.entities;
        tweet.display_text_range = undefined; // no text range for long tweets
    }
    if(tweet.quoted_status_result && tweet.quoted_status_result.result) {
        let result = tweet.quoted_status_result.result;
        if(!result.core && result.tweet) result = result.tweet;
        if(result.limitedActionResults) {
            let limitation = result.limitedActionResults.limited_actions.find(l => l.action === "Reply");
            if(limitation) {
                result.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message;
            }
            result = result.tweet;
        }
        tweet.quoted_status = result.legacy;
        if(tweet.quoted_status) {
            tweet.quoted_status.user = result.core.user_results.result.legacy;
            if(!tweet.quoted_status.user) {
                delete tweet.quoted_status;
            } else {
                tweet.quoted_status.user.id_str = tweet.quoted_status.user_id_str;
                if(result.core.user_results.result.is_blue_verified && !result.core.user_results.result.legacy.verified_type) {
                    tweet.quoted_status.user.verified = true;
                    tweet.quoted_status.user.verified_type = "Blue";
                }
                tweet.quoted_status.ext = {};
                if(result.views) {
                    tweet.quoted_status.ext.views = {r: {ok: {count: +result.views.count}}};
                }
                tweetStorage[tweet.quoted_status.id_str] = tweet.quoted_status;
                tweetStorage[tweet.quoted_status.id_str].cacheDate = Date.now();
                userStorage[tweet.quoted_status.user.id_str] = tweet.quoted_status.user;
                userStorage[tweet.quoted_status.user.id_str].cacheDate = Date.now();
            }
        } else {
            console.warn("No quoted status", result);
        }
    }
    if(res.card && res.card.legacy) {
        tweet.card = res.card.legacy;
        let bvo = {};
        for(let i = 0; i < tweet.card.binding_values.length; i++) {
            let bv = tweet.card.binding_values[i];
            bvo[bv.key] = bv.value;
        }
        tweet.card.binding_values = bvo;
    }
    if(res.views) {
        if(!tweet.ext) tweet.ext = {};
        tweet.ext.views = {r: {ok: {count: +res.views.count}}};
    }
    if(res.source) {
        tweet.source = res.source;
    }
    if(res.birdwatch_pivot) { // community notes
        tweet.birdwatch = res.birdwatch_pivot;
    }
    if(res.trusted_friends_info_result && res.trusted_friends_info_result.owner_results && res.trusted_friends_info_result.owner_results.result && res.trusted_friends_info_result.owner_results.result.legacy) {
        tweet.trusted_circle_owner = res.trusted_friends_info_result.owner_results.result.legacy.screen_name;
    }

    if(tweet.favorited && tweet.favorite_count === 0) {
        tweet.favorite_count = 1;
    }
    if(tweet.retweeted && tweet.retweet_count === 0) {
        tweet.retweet_count = 1;
    }

    tweet.res = res;

    tweetStorage[tweet.id_str] = tweet;
    tweetStorage[tweet.id_str].cacheDate = Date.now();
    userStorage[tweet.user.id_str] = tweet.user;
    userStorage[tweet.user.id_str].cacheDate = Date.now();
    return tweet;
}


const API = {
    account: {
        verifyCredentials: () => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/account/verify_credentials.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session"
                    },
                    credentials: "include"
                }).then(response => response.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
    },
    user: {
        get: (val, byId = true) => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/users/show.json?${byId ? `user_id=${val}` : `screen_name=${val}`}`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "x-twitter-client-language": window.LANGUAGE ? window.LANGUAGE : navigator.language ? navigator.language : "en"
                    },
                    credentials: "include"
                }).then(i => {
                    if(i.status === 401) {
                        setTimeout(() => {
                            location.href = `/i/flow/login?newtwitter=true`;
                        }, 50);
                    }
                    return i.json();
                }).then(data => {
                    debugLog('user.get', {val, byId, data});
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        getV2: name => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/sLVLhk0bGj3MVFEKTdax1w/UserByScreenName?variables=%7B%22screen_name%22%3A%22${name}%22%2C%22withSafetyModeUserFields%22%3Atrue%2C%22withSuperFollowsUserFields%22%3Atrue%7D&features=${encodeURIComponent(JSON.stringify({"blue_business_profile_image_shape_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true}))}`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json",
                        "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    debugLog('user.getV2', 'start', {name, data});
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    if(data.data.user.result.unavailable_message) {
                        return reject(data.data.user.result.unavailable_message.text);
                    }

                    let result = data.data.user.result;
                    result.legacy.id_str = result.rest_id;
                    if(result.legacy_extended_profile.birthdate) {
                        result.legacy.birthdate = result.legacy_extended_profile.birthdate;
                    }
                    if(result.professional) {
                        result.legacy.professional = result.professional;
                    }
                    if(result.affiliates_highlighted_label && result.affiliates_highlighted_label.label) {
                        result.legacy.affiliates_highlighted_label = result.affiliates_highlighted_label.label;
                    }
                    if(result.is_blue_verified && !result.legacy.verified_type) {
                        result.legacy.verified_type = "Blue";
                    }
        
                    debugLog('user.getV2', 'end', result.legacy);
                    resolve(result.legacy);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        follow: screen_name => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/friendships/create.json`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    credentials: "include",
                    body: `screen_name=${screen_name}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unfollow: screen_name => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/friendships/destroy.json`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    credentials: "include",
                    body: `screen_name=${screen_name}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        receiveNotifications: (id, receive = false) => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/friendships/update.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&include_ext_has_nft_avatar=1&skip_status=1&cursor=-1&id=${id}&device=${receive}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        block: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/blocks/create.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unblock: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/blocks/destroy.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }

                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        mute: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/mutes/users/create.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unmute: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/mutes/users/destroy.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }

                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        removeFollower: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/QpNfg0kpPRfjROQ_9eOLXA/RemoveFollower`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json"
                    },
                    credentials: "include",
                    method: 'post',
                    body: JSON.stringify({"variables":{"target_user_id":id},"queryId":"QpNfg0kpPRfjROQ_9eOLXA"})
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        lookup: ids => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/users/lookup.json?user_id=${ids.join(",")}`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        getFollowersYouFollow: (id, cursor) => {
            return new Promise((resolve, reject) => {
                let obj = {
                    "userId": id,
                    "count": 50,
                    "includePromotedContent": false
                };
                if(cursor) obj.cursor = cursor;
                GM_fetch(`/i/api/graphql/m8AXvuS9H0aAI09J3ISOrw/FollowersYouKnow?variables=${encodeURIComponent(JSON.stringify(obj))}&features=${encodeURIComponent(JSON.stringify({"rweb_lists_timeline_redesign_enabled":false,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}))}`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    debugLog('user.getFollowersYouFollow', 'start', {id, cursor, data});
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    let list = data.data.user.result.timeline.timeline.instructions.find(i => i.type === 'TimelineAddEntries').entries;
                    const out = {
                        list: list.filter(e => e.entryId.startsWith('user-')).map(e => {
                            let user = e.content.itemContent.user_results.result;
                            user.legacy.id_str = user.rest_id;
                            if(user.is_blue_verified && !user.legacy.verified_type) {
                                user.legacy.verified = true;
                                user.legacy.verified_type = "Blue";
                            }
                            return user.legacy;
                        }),
                        cursor: list.find(e => e.entryId.startsWith('cursor-bottom-')).content.value
                    };
                    debugLog('user.getFollowersYouFollow', 'end', out);
                    resolve(out);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        switchRetweetsVisibility: (user_id, see) => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/friendships/update.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `id=${user_id}&retweets=${see}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        getFollowRequests: (cursor = -1) => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/friendships/incoming.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&include_ext_has_nft_avatar=1&skip_status=1&cursor=${cursor}&stringify_ids=true&count=100`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        acceptFollowRequest: user_id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/friendships/accept.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${user_id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        declineFollowRequest: user_id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/friendships/deny.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `user_id=${user_id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
    },
    tweet: {
        post: data => { // deprecated
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/statuses/update.json`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    body: new URLSearchParams(data).toString(),
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        /* 
            text | tweet_text | status - tweet text
            media | media_ids - media ids
            card_uri - card uri
            sensitive - sensitive media
            in_reply_to_status_id | in_reply_to_tweet_id - reply to tweet id
            exclude_reply_user_ids - exclude mentions
            attachment_url - quote tweet url
            circle - circle id
            conversation_control - conversation control (follows | mentions)
        */
        postV2: tweet => {
            return new Promise((resolve, reject) => {
                let text;
                if(tweet.text) {
                    text = tweet.text;
                } else if(tweet.tweet_text) {
                    text = tweet.tweet_text;
                } else if(tweet.status) {
                    text = tweet.status;
                } else {
                    text = "";
                }
                let variables = {
                    "tweet_text": text,
                    "media": {
                        "media_entities": [],
                        "possibly_sensitive": false
                    },
                    "semantic_annotation_ids": [],
                    "dark_request": false
                };
                if(tweet.card_uri) {
                    variables.card_uri = tweet.card_uri;
                }
                if(tweet.media_ids) {
                    if(typeof tweet.media_ids === "string") {
                        tweet.media = tweet.media_ids.split(",");
                    } else {
                        tweet.media = tweet.media_ids;
                    }
                }
                if(tweet.media) {
                    variables.media.media_entities = tweet.media.map(i => ({media_id: i, tagged_users: []}));
                    if(tweet.sensitive) {
                        variables.media.possibly_sensitive = true;
                    }
                }
                if(tweet.conversation_control === 'follows') {
                    variables.conversation_control = { mode: 'Community' };
                } else if(tweet.conversation_control === 'mentions') {
                    variables.conversation_control = { mode: 'ByInvitation' };
                }
                if(tweet.circle) {
                    variables.trusted_friends_control_options = { "trusted_friends_list_id": tweet.circle };
                }
                if(tweet.in_reply_to_status_id) {
                    tweet.in_reply_to_tweet_id = tweet.in_reply_to_status_id;
                    delete tweet.in_reply_to_status_id;
                }
                if(tweet.in_reply_to_tweet_id) {
                    variables.reply = {
                        in_reply_to_tweet_id: tweet.in_reply_to_tweet_id,
                        exclude_reply_user_ids: []
                    }
                    if(tweet.exclude_reply_user_ids) {
                        if(typeof tweet.exclude_reply_user_ids === "string") {
                            tweet.exclude_reply_user_ids = tweet.exclude_reply_user_ids.split(",");
                        }
                        variables.reply.exclude_reply_user_ids = tweet.exclude_reply_user_ids;
                    }
                }
                if(tweet.attachment_url) {
                    variables.attachment_url = tweet.attachment_url;
                }
                debugLog('tweet.postV2', 'init', {tweet, variables});
                let parsedTweet = twttr.txt.parseTweet(text);
                GM_fetch(`/i/api/graphql/${parsedTweet.weightedLength > 280 ? 'cuvrhmg0s4pGaLWV68NNnQ/CreateNoteTweet' : 'I_J3_LvnnihD0Gjbq5pD2g/CreateTweet'}`, {
                    method: 'POST',
                    headers: {
                        "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw",
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8",
                        "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en"
                    },
                    credentials: "include",
                    body: JSON.stringify({
                        variables,
                        "features": {"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"responsive_web_home_pinned_timelines_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_enhance_cards_enabled":false},
                        "queryId": parsedTweet.weightedLength > 280 ? 'cuvrhmg0s4pGaLWV68NNnQ' : 'I_J3_LvnnihD0Gjbq5pD2g'
                    })
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.postV2', 'start', data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    let ct = data.data.create_tweet ? data.data.create_tweet : data.data.notetweet_create;
                    let result = ct.tweet_results.result;
                    let tweet = parseTweet(result);
                    if(result.trusted_friends_info_result && !tweet.limited_actions) {
                        tweet.limited_actions = 'limit_trusted_friends_tweet';
                    }
                    debugLog('tweet.postV2', 'end', tweet);
                    resolve(tweet);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        favorite: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweet_id":id},"queryId":"lI07N6Otwv1PhnEgXILM7A"})
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unfavorite: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweet_id":id},"queryId":"ZYKSe-w7KEslx3JhSIk5LA"})
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        retweet: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweet_id":id,"dark_request":false},"queryId":"ojPdsZsimiJrUGLR1sjUtA"})
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.retweet', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unretweet: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"source_tweet_id":id,"dark_request":false},"queryId":"iQtK4dl5hBmXewYZuEOKVw"})
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.unretweet', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        delete: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweet_id":id,"dark_request":false},"queryId":"VaenaVgh5q5ih7kvyVjgtg"})
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.delete', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        get: id => { // deprecated
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/statuses/show.json?id=${id}&include_my_retweet=1&cards_platform=Web13&include_entities=1&include_user_entities=1&include_cards=1&send_error_codes=1&tweet_mode=extended&include_ext_alt_text=true&include_reply_count=true&ext=views%2CmediaStats%2CverifiedType%2CisBlueVerified`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        vote: (api, tweet_id, card_uri, card_name, selected_choice) => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://caps.${location.hostname}/v2/capi/${api.split('//')[1]}`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `twitter%3Astring%3Acard_uri=${encodeURIComponent(card_uri)}&twitter%3Along%3Aoriginal_tweet_id=${tweet_id}&twitter%3Astring%3Aresponse_card_name=${card_name}&twitter%3Astring%3Acards_platform=Web-12&twitter%3Astring%3Aselected_choice=${selected_choice}`
                }).then(response => response.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            })
        },
        createCard: card_data => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://caps.${location.hostname}/v2/cards/create.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `card_data=${encodeURIComponent(JSON.stringify(card_data))}`
                }).then(response => response.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            })
        },
        mute: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/mutes/conversations/create.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `tweet_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unmute: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/mutes/conversations/destroy.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `tweet_id=${id}`
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        lookup: ids => {
            return new Promise((resolve, reject) => {
                GM_fetch(`https://api.${location.hostname}/1.1/statuses/lookup.json?id=${ids.join(',')}&include_entities=true&include_ext_alt_text=true&include_card_uri=true&tweet_mode=extended&include_reply_count=true&ext=views%2CmediaStats`, {
                    headers: {
                        "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF",
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "x-twitter-client-language": navigator.language ? navigator.language : "en"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    if (data.errors && data.errors[0].code === 32) {
                        return reject("Not logged in");
                    }
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        pin: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/account/pin_tweet.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `id=${id}`
                }).then(i => i.text()).then(data => {
                    resolve(true);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unpin: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/1.1/account/unpin_tweet.json`, {
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded; charset=UTF-8"
                    },
                    credentials: "include",
                    method: 'post',
                    body: `id=${id}`
                }).then(i => i.text()).then(data => {
                    resolve(true);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        moderate: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/pjFnHGVqCjTcZol0xcBJjw/ModerateTweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweetId":id},"queryId":"pjFnHGVqCjTcZol0xcBJjw"})
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.moderate', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        unmoderate: id => {
            return new Promise((resolve, reject) => {
                GM_fetch(`/i/api/graphql/pVSyu6PA57TLvIE4nN2tsA/UnmoderateTweet`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/json; charset=utf-8"
                    },
                    credentials: "include",
                    body: JSON.stringify({"variables":{"tweetId":"1683331680751308802"},"queryId":"pVSyu6PA57TLvIE4nN2tsA"})
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.unmoderate', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        },
        getModeratedReplies: (id, cursor) => {
            return new Promise((resolve, reject) => {
                let variables = {"rootTweetId":id,"count":20,"includePromotedContent":false};
                if(cursor) variables.cursor = cursor;
                GM_fetch(`/i/api/graphql/SiKS1_3937rb72ytFnDHmA/ModeratedTimeline?variables=${encodeURIComponent(JSON.stringify(variables))}&features=${encodeURIComponent(JSON.stringify({"rweb_lists_timeline_redesign_enabled":false,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}))}`, {
                    method: 'POST',
                    headers: {
                        "authorization": publicToken,
                        "x-csrf-token": getCsrf(),
                        "x-twitter-auth-type": "OAuth2Session",
                        "content-type": "application/x-www-form-urlencoded",
                        "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en"
                    },
                    credentials: "include"
                }).then(i => i.json()).then(data => {
                    debugLog('tweet.getModeratedReplies', 'start', id, data);
                    if (data.errors && data.errors[0]) {
                        return reject(data.errors[0].message);
                    }
                    let entries = data.data.tweet.result.timeline_response.timeline.instructions.find(i => i.entries);
                    if(!entries) return resolve({
                        list: [],
                        cursor: undefined
                    });
                    entries = entries.entries;
                    let list = entries.filter(e => e.entryId.startsWith('tweet-'));
                    let cursor = entries.find(e => e.entryId.startsWith('cursor-bottom'));
                    if(!cursor) {
                        let entries = data.data.tweet.result.timeline_response.timeline.instructions.find(i => i.replaceEntry && i.replaceEntry.entryIdToReplace.includes('cursor-bottom'));
                        if(entries) {
                            cursor = entries.replaceEntry.entry.content.operation.cursor.value;
                        }
                    } else {
                        cursor = cursor.content.operation.cursor.value;
                    }
                    let out = {
                        list: list.map(e => {
                            let tweet = parseTweet(e.content.itemContent.tweet_results.result);
                            if(!tweet) return;
                            tweet.moderated = true;
                            return tweet;
                        }).filter(e => e),
                        cursor
                    };
                    debugLog('tweet.getModeratedReplies', 'end', id, out);
                    resolve(data);
                }).catch(e => {
                    reject(e);
                });
            });
        }
    },
};

// scripts/helpers.js
function createModal(html, className, onclose, canclose) {
    let modal = document.createElement('div');
    modal.classList.add('yeah-modal');
    let modal_content = document.createElement('div');
    modal_content.classList.add('yeah-modal-content');
    if(className) modal_content.classList.add(className);
    modal_content.innerHTML = html;
    modal.appendChild(modal_content);
    let close = document.createElement('span');
    close.classList.add('yeah-modal-close');
    close.title = "ESC";
    close.innerHTML = '&times;';
    document.body.style.overflowY = 'hidden';
    function removeModal() {
        modal.remove();
        let event = new Event('findActiveTweet');
        document.dispatchEvent(event);
        document.removeEventListener('keydown', escapeEvent);
        if(onclose) onclose();
        let modals = document.getElementsByClassName('modal');
        if(modals.length === 0) {
            document.body.style.overflowY = '';
        }
    }
    modal.removeModal = removeModal;
    function escapeEvent(e) {
        if(document.querySelector('.viewer-in')) return;
        if(e.key === 'Escape' || (e.altKey && e.keyCode === 78)) {
            if(!canclose || canclose()) removeModal();
        }
    }
    close.addEventListener('click', removeModal);
    let isHoldingMouseFromContent = false;
    modal_content.addEventListener('mousedown', () => {
        isHoldingMouseFromContent = true;
    });
    document.addEventListener('mouseup', () => {
        setTimeout(() => isHoldingMouseFromContent = false, 10);
    });
    modal.addEventListener('click', e => {
        if(e.target === modal && !isHoldingMouseFromContent) {
            if(!canclose || canclose()) removeModal();
        }
    });
    document.addEventListener('keydown', escapeEvent);
    modal_content.appendChild(close);
    document.body.appendChild(modal);
    return modal;
}

async function callTwitterApi(method = 'GET', path, headers = {}, body) {
    if(typeof body === 'object' && !headers['Content-Type']) {
        body = JSON.stringify(body);
        headers['Content-Type'] = 'application/json';
    }
    if(headers['Content-Type'] === 'application/x-www-form-urlencoded') {
        body = new URLSearchParams(body).toString();
    }
    if(!headers['Authorization']) {
        headers['Authorization'] = `Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA`;
    }
    if(!headers['x-csrf-token']) {
        let csrf = document.cookie.match(/(?:^|;\s*)ct0=([0-9a-f]+)\s*(?:;|$)/);
        headers['x-csrf-token'] = csrf ? csrf[1] : "";
    }
    headers['x-twitter-auth-type'] = 'OAuth2Session';
    headers['x-twitter-active-user'] = 'yes';
    headers['x-twitter-client-language'] = 'en';

    let res = await GM_fetch(`https://${location.hostname}/i/api${path}`, {
        method,
        headers,
        body
    }).then(res => res.json());

    if(res.errors) {
        throw new Error(res.errors[0].message);
    }

    return res;
};

async function callYeahApi(path, body = {}) {
    let token = await getYeahToken();
    if(token) body.key = token;

    const res = await GM_fetch(API_URL + path, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(body)
    });
    let result = await res.text();

    if(result === 'Invalid key') {
        chrome.storage.local.remove('yeahToken');
        chrome.storage.local.get('yeahTokens', async result => {
            if(result.yeahTokens) {
                let userId = await getUserId();
                delete result.yeahTokens[userId];
                chrome.storage.local.set(result);
            }
        });
        throw new Error('Invalid key');
    }

    return result;
}

let _userId;
async function getUserId() {
    if(!_userId) {
        let user = await API.account.verifyCredentials();
        _userId = user.id_str;
    }
    return _userId;
}

function getYeahToken() {
    return new Promise(async (resolve, reject) => {
        chrome.storage.local.get(['yeahToken', 'yeahTokens'], async result => {
            if(result) {
                let userId = await getUserId();
                if(result.yeahTokens && result.yeahTokens[userId]) {
                    resolve(result.yeahTokens[userId]);
                } else {
                    resolve(result.yeahToken);
                }
            } else {
                resolve(null);
            } 
        });
    });
}

function getYeahSettings() {
    return new Promise((resolve, reject) => {
        chrome.storage.local.get('settings', result => {
            if(result && result.settings) {
                resolve(result.settings);
            } else {
                resolve({});
            } 
        });
    });
}

function formatLargeNumber(n) {
    let option = {notation: 'compact', compactDisplay: 'short', maximumFractionDigits: 1, minimumFractionDigits: 1};
    if (n >= 1e3) {
        return Number(n).toLocaleString('en-US', option);
    }
    else return Number(n).toLocaleString();
}

function escapeHTML(unsafe) {
    if(typeof unsafe === 'undefined' || unsafe === null) {
        return '';
    }
    return DOMPurify.sanitize(String(unsafe));
}

async function appendUser(u, container, label) {
    let userElement = document.createElement('div');
    userElement.classList.add('user-item');

    userElement.innerHTML = /*html*/`
        <div>
            <a href="/${u.screen_name}" class="user-item-link" target="_blank">
                <img src="${u.profile_image_url_https}" alt="${u.screen_name}" class="user-item-avatar tweet-avatar" width="48" height="48">
                <div class="user-item-text">
                    <span class="yeah-name user-item-name${u.protected ? ' user-protected' : ''}${u.muting ? ' user-muted' : ''}${u.verified || u.verified_type ? ' user-verified' : u.id_str === '1708130407663759360' ? ' user-verified user-verified-dimden' : ''} ${u.verified_type === 'Government' ? 'user-verified-gray' : u.verified_type === 'Business' ? 'user-verified-yellow' : u.verified_type === 'Blue' ? 'user-verified-blue' : ''}">${escapeHTML(u.name)}</span><br>
                    <span class="yeah-handle">@${u.screen_name}</span>
                    ${u.followed_by ? `<span class="follows-you-label">Follows you</span>` : ''}
                    ${label ? `<br><span class="user-item-additional">${escapeHTML(label)}</span>` : ''}
                </div>
            </a>
        </div>
        <button class="user-yeah-item-btn nice-yeah-button ${u.following ? 'yeah-following' : 'yeah-follow'}">${u.following ? "Following" : "Follow"}</button>
    `;

    let followButton = userElement.querySelector('.user-yeah-item-btn');
    followButton.addEventListener('click', async () => {
        if (followButton.classList.contains('yeah-following')) {
            try {
                await API.user.unfollow(u.screen_name);
            } catch(e) {
                console.error(e);
                alert(e);
                return;
            }
            followButton.classList.remove('yeah-following');
            followButton.classList.add('yeah-follow');
            followButton.innerText = "Follow";
        } else {
            try {
                await API.user.follow(u.screen_name);
            } catch(e) {
                console.error(e);
                alert(e);
                return;
            }
            followButton.classList.remove('yeah-follow');
            followButton.classList.add('yeah-following');
            followButton.innerText = "Following";
        }
    });

    container.appendChild(userElement);
}


// scripts/tweetrenderer.js

let lastTweetErrorDate = 0;
const mediaClasses = [
    undefined,
    'tweet-media-element-one',
    'tweet-media-element-two',
    'tweet-media-element-three',
    'tweet-media-element-two',
];

function calculateSize(x, y, max_x, max_y) {
    let ratio = x / y;
    let iw = innerWidth;
    if(iw < 590) max_x = iw - 120;
    if(x > max_x) {
        x = max_x;
        y = x / ratio;
    }
    if(y > max_y) {
        y = max_y;
        x = y * ratio;
    }
    return [parseInt(x), parseInt(y)];
}

const sizeFunctions = [
    undefined,
    (w, h) => calculateSize(w, h, 450, 500),
    (w, h) => calculateSize(w, h, 225, 400),
    (w, h) => innerWidth < 590 ? calculateSize(w, h, 225, 400) : calculateSize(w, h, 150, 250),
    (w, h) => calculateSize(w, h, 225, 400),
    (w, h) => calculateSize(w, h, 225, 400),
    (w, h) => calculateSize(w, h, 225, 400),
    (w, h) => calculateSize(w, h, 225, 400),
    (w, h) => calculateSize(w, h, 225, 400)
];

const quoteSizeFunctions = [
    undefined,
    (w, h) => calculateSize(w, h, 400, 400),
    (w, h) => calculateSize(w, h, 200, 400),
    (w, h) => calculateSize(w, h, 125, 200),
    (w, h) => calculateSize(w, h, 100, 150),
    (w, h) => calculateSize(w, h, 100, 150),
    (w, h) => calculateSize(w, h, 100, 150),
    (w, h) => calculateSize(w, h, 100, 150),
    (w, h) => calculateSize(w, h, 100, 150)
];

function html(strings, ...values) {
    let str = '';
    strings.forEach((string, i) => {
        str += string + escapeHTML(values[i]);
    });
    return str;
}

async function handleFiles(files, mediaArray, mediaContainer, is_dm = false) {
    let images = [];
    let videos = [];
    let gifs = [];
    for (let i = 0; i < files.length; i++) {
        let file = files[i];
        if (file.type.includes('gif')) {
            // max 15 mb
            if (file.size > 15000000) {
                return alert("Gifs max size is 15mb");
            }
            gifs.push(file);
        } else if (file.type.includes('video')) {
            // max 500 mb
            if (file.size > 500000000) {
                return alert("Videos max size is 500mb");
            }
            videos.push(file);
        } else if (file.type.includes('image')) {
            // max 5 mb
            if (
                file.size > 5000000 ||
                (window.navigator && navigator.connection && navigator.connection.type === 'cellular')
            ) {
                // convert png to jpeg
                let toBreak = false, i = 0;
                while(file.size > 5000000) {
                    await new Promise(resolve => {
                        let canvas = document.createElement('canvas');
                        let ctx = canvas.getContext('2d');
                        let img = new Image();
                        img.onload = function () {
                            canvas.width = img.width;
                            canvas.height = img.height;
                            ctx.drawImage(img, 0, 0);
                            let dataURL = canvas.toDataURL('image/jpeg', (window.navigator && navigator.connection && navigator.connection.type === 'cellular') ? (0.5 - i*0.1) : (0.9 - i*0.1));
                            let blobBin = atob(dataURL.split(',')[1]);
                            let array = [];
                            for (let i = 0; i < blobBin.length; i++) {
                                array.push(blobBin.charCodeAt(i));
                            }
                            let newFile = new Blob([new Uint8Array(array)], { type: 'image/jpeg' });
                            if(newFile.size > file.size) {
                                toBreak = true;
                            } else {
                                file = newFile;
                            }
                            resolve();
                        };
                        img.src = URL.createObjectURL(file);
                    });
                    if(toBreak || i++ > 5) break;
                }
                if(file.size > 5000000) {
                    return alert("Images max size is 5mb");
                }
            }
            images.push(file);
        }
    }
    // either up to 4 images or 1 video or 1 gif
    if (images.length > 0) {
        if (images.length > 4) {
            images = images.slice(0, 4);
        }
        if (videos.length > 0 || gifs.length > 0) {
            return alert("Images and videos max count is 4");
        }
    }
    if (videos.length > 0) {
        if (images.length > 0 || gifs.length > 0 || videos.length > 1) {
            return alert("Videos max count is 1");
        }
    }
    if (gifs.length > 0) {
        if (images.length > 0 || videos.length > 0 || gifs.length > 1) {
            return alert("Gifs max count is 1");
        }
    }
    // get base64 data
    let media = [...images, ...videos, ...gifs];
    let base64Data = [];
    for (let i = 0; i < media.length; i++) {
        let file = media[i];
        let reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.onload = () => {
            base64Data.push(reader.result);
            if (base64Data.length === media.length) {
                while (mediaArray.length >= 4) {
                    mediaArray.pop();
                    mediaContainer.lastChild.remove();
                }
                base64Data.forEach(data => {
                    let div = document.createElement('div');
                    let img = document.createElement('img');
                    div.title = file.name;
                    div.id = `new-tweet-media-img-${Date.now()}${Math.random()}`.replace('.', '-');
                    div.className = "new-tweet-media-img-div";
                    img.className = "new-tweet-media-img";
                    let progress = document.createElement('span');
                    progress.hidden = true;
                    progress.className = "new-tweet-media-img-progress";
                    let remove = document.createElement('span');
                    remove.className = "new-tweet-media-img-remove";
                    let alt;
                    if (!file.type.includes('video')) {
                        alt = document.createElement('span');
                        alt.className = "new-tweet-media-img-alt";
                        alt.innerText = "ALT";
                        alt.addEventListener('click', () => {
                            mediaObject.alt = prompt("Alt text", mediaObject.alt || '');
                        });
                    }
                    let cw = document.createElement('span');
                    cw.className = "new-tweet-media-img-cw";
                    cw.innerText = "CW";
                    cw.addEventListener('click', () => {
                        createModal(`
                            <div class="cw-modal" style="color:var(--almost-black)">
                                <h2 class="nice-header">Content warnings</h2>
                                <br>
                                <input type="checkbox" id="cw-modal-graphic_violence"${mediaObject.cw.includes('graphic_violence') ? ' checked' : ''}> <label for="cw-modal-graphic_violence">Graphic violence</label><br>
                                <input type="checkbox" id="cw-modal-adult_content"${mediaObject.cw.includes('adult_content') ? ' checked' : ''}> <label for="cw-modal-adult_content">Adult content</label><br>
                                <input type="checkbox" id="cw-modal-other"${mediaObject.cw.includes('other') ? ' checked' : ''}> <label for="cw-modal-other">Sensitive content</label><br>
                            </div>
                        `);
                        let graphic_violence = document.getElementById('cw-modal-graphic_violence');
                        let adult_content = document.getElementById('cw-modal-adult_content');
                        let sensitive_content = document.getElementById('cw-modal-other');
                        [graphic_violence, adult_content, sensitive_content].forEach(checkbox => {
                            checkbox.addEventListener('change', () => {
                                if (checkbox.checked) {
                                    mediaObject.cw.push(checkbox.id.slice(9));
                                } else {
                                    let index = mediaObject.cw.indexOf(checkbox.id.slice(9));
                                    if (index > -1) {
                                        mediaObject.cw.splice(index, 1);
                                    }
                                }
                            });
                        });
                    });

                    let mediaObject = {
                        div, img,
                        id: div.id,
                        data: data,
                        type: file.type,
                        cw: [],
                        category: file.type.includes('gif') ? (is_dm ? 'dm_gif' : 'tweet_gif') : file.type.includes('video') ? (is_dm ? 'dm_video' : 'tweet_video') : (is_dm ? 'dm_image' : 'tweet_image')
                    };
                    mediaArray.push(mediaObject);
                    if(file.type.includes('video')) {
                        img.src = 'data:image/png;base64,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';
                    } else {
                        let dataBase64 = arrayBufferToBase64(data);
                        img.src = `data:${file.type};base64,${dataBase64}`;
                    }
                    remove.addEventListener('click', () => {
                        div.remove();
                        for (let i = mediaArray.length - 1; i >= 0; i--) {
                            let m = mediaArray[i];
                            if (m.id === div.id) mediaArray.splice(i, 1);
                        }
                    });
                    div.append(img, progress, remove);
                    if (!file.type.includes('video')) {
                        img.addEventListener('click', () => {
                            new Viewer(mediaContainer, {
                                transition: false,
                                zoomRatio: 0.3
                            });
                        });
                        div.append(alt);
                    } else {
                        cw.style.marginLeft = '-53px';
                    }
                    div.append(cw);
                    mediaContainer.append(div);
                });
                
                setTimeout(() => {
                    let messageModalElement = document.getElementsByClassName('messages-container')[0];
                    let inboxModalElement = document.getElementsByClassName('inbox-modal')[0];
                    if(messageModalElement) inboxModalElement.scrollTop = inboxModalElement.scrollHeight;
                }, 10);
            }
        }
    }
}
let isURL = (str) => {
    try {
        new URL(str);
        return true;
    } catch (_) {
        return false;
    }
}
function handleDrop(event, mediaArray, mediaContainer) {
    let text = event.dataTransfer.getData("Text").trim();
    if(text.length <= 1) {
        event.stopPropagation();
        event.preventDefault();
        let files = event.dataTransfer.files;
        handleFiles(files, mediaArray, mediaContainer);
    }
}
function getMedia(mediaArray, mediaContainer, is_dm = false) {
    let input = document.createElement('input');
    input.type = 'file';
    input.multiple = true;
    input.accept = 'image/jpeg,image/png,image/webp,image/gif,video/mp4,video/quicktime';
    input.addEventListener('change', () => {
        handleFiles(input.files, mediaArray, mediaContainer, is_dm);
    });
    input.click();
};
function timeElapsed(targetTimestamp) {
    let currentDate = new Date();
    let currentTimeInms = currentDate.getTime();
    let targetDate = new Date(targetTimestamp);
    let targetTimeInms = targetDate.getTime();
    let elapsed = Math.floor((currentTimeInms - targetTimeInms) / 1000);

    if (elapsed < 1) {
        return 'now';
    }
    if (elapsed < 60) { //< 60 sec
        return `${elapsed}s`;
    }
    if (elapsed < 3600) { //< 60 minutes
        return `${Math.floor(elapsed / (60))}m`;
    }
    if (elapsed < 86400) { //< 24 hours
        return `${Math.floor(elapsed / (3600))}h`;
    }
    if (elapsed < 604800) { //<7 days
        return `${Math.floor(elapsed / (86400))}d`;
    }
    if (targetDate.getFullYear() == currentDate.getFullYear()) { // same years
        return targetDate.toLocaleDateString("en-US", { month: 'long', day: 'numeric' });
    }
    //more than last years
    return targetDate.toLocaleDateString("en-US", { year: 'numeric', month: 'long', day: 'numeric' });
}


async function renderTweetBodyHTML(t, is_quoted) {
    let result = "",
        last_pos = 0,
        index_map = {}; // {start_position: [end_position, replacer_func]}
        hashflags = [];

    if(is_quoted) t = t.quoted_status;

    full_text_array = Array.from(t.full_text);

    if (t.entities.richtext) {
        t.entities.richtext.forEach(snippet => {
            //if i felt like it, id write a long-winded series of comments on how much i hate emojis. but i'll refrain
            //and this *still* doesnt work properly with flag emojis
            //im just glad it works at all

            let textBeforeSnippet = t.full_text.slice(0, snippet.from_index);
            let emojisBeforeSnippet = textBeforeSnippet.match(/\p{Extended_Pictographic}/gu);
            emojisBeforeSnippet = emojisBeforeSnippet ? emojisBeforeSnippet.length : 0;

            let fromIndex = snippet.from_index - emojisBeforeSnippet;
            let toIndex = snippet.to_index - emojisBeforeSnippet;

            index_map[fromIndex] = [
                toIndex,
                text => {
                    let snippetText = escapeHTML(full_text_array.slice(fromIndex, toIndex).join(''));
                    let startingTags = `${snippet.richtext_types.includes('Bold') ? '<b>' : ''}${snippet.richtext_types.includes('Italic') ? '<i>' : ''}`;
                    let endingTags = `${snippet.richtext_types.includes('Bold') ? '</b>' : ''}${snippet.richtext_types.includes('Italic') ? '</i>' : ''}`;

                    return `${startingTags}${snippetText}${endingTags}`;
                }
            ];
        });
    }

    if (is_quoted) { // for quoted tweet we need only hashflags and readable urls
        if (t.entities.hashtags) {
            t.entities.hashtags.forEach(hashtag => {
                let hashflag = hashflags.find(h => h.hashtag.toLowerCase() === hashtag.text.toLowerCase());
                index_map[hashtag.indices[0]] = [hashtag.indices[1], text =>
                    `#${escapeHTML(hashtag.text)}`+
                    `${hashflag ? `<img src="${hashflag.asset_url}" class="hashflag">` : ''}`];
            });
        };

        if (t.entities.urls) {
            t.entities.urls.forEach(url => {
                index_map[url.indices[0]] = [url.indices[1], text => `${escapeHTML(url.display_url)}`];
            });
        };
    } else {
        if (t.entities.hashtags) {
            t.entities.hashtags.forEach(hashtag => {
                let hashflag = hashflags.find(h => h.hashtag.toLowerCase() === hashtag.text.toLowerCase());
                index_map[hashtag.indices[0]] = [hashtag.indices[1], text => `<a href="/hashtag/${escapeHTML(hashtag.text)}">`+
                    `#${escapeHTML(hashtag.text)}`+
                    `${hashflag ? `<img src="${hashflag.asset_url}" class="hashflag">` : ''}`+
                `</a>`];
            });
        };

        if (t.entities.symbols) {
            t.entities.symbols.forEach(symbol => {
                index_map[symbol.indices[0]] = [symbol.indices[1], text => `<a href="/search?q=%24${escapeHTML(symbol.text)}">`+
                    `$${escapeHTML(symbol.text)}`+
                `</a>`];
            });
        }

        if (t.entities.urls) {
            t.entities.urls.forEach(url => {
                index_map[url.indices[0]] = [url.indices[1], text =>
                    `<a href="${escapeHTML(url.expanded_url)}" title="${escapeHTML(url.expanded_url)}" target="_blank" rel="noopener noreferrer">`+
                    `${escapeHTML(url.display_url)}</a>`];
            });
        };

        if (t.entities.user_mentions) {
            t.entities.user_mentions.forEach(user => {
                index_map[user.indices[0]] = [user.indices[1], text => `<a href="/${escapeHTML(user.screen_name)}">${escapeHTML(text)}</a>`];
            });
        };

        if(t.entities.media) {
            t.entities.media.forEach(media => {
                index_map[media.indices[0]] = [media.indices[1], text => ``];
            });
        }
    };

    let display_start = t.display_text_range !== undefined ? t.display_text_range[0] : 0;
    let display_end   = t.display_text_range !== undefined ? t.display_text_range[1] : full_text_array.length;
    for (let [current_pos, _] of full_text_array.entries()) {
        if (current_pos < display_start) { // do not render first part of message
            last_pos = current_pos + 1; // to start copy from next symbol
            continue;
        }
        if (current_pos == display_end ||                // reached the end of visible part
            current_pos == full_text_array.length - 1) { // reached the end of tweet itself
                if (display_end == full_text_array.length) current_pos++; // dirty hack to include last element of slice
                result += escapeHTML(full_text_array.slice(last_pos, current_pos).join(''));
                break;
        }
        if (current_pos > display_end) {
            break; // do not render last part of message
        }

        if (current_pos in index_map) {
            let [end, func] = index_map[current_pos];
            
            if (current_pos > last_pos) {
                result += escapeHTML(full_text_array.slice(last_pos, current_pos).join('')); // store chunk of untouched text
            }
            result += func(full_text_array.slice(current_pos, end).join('')); // run replacer func on corresponding range
            last_pos = end;
        }
    }
    return result
}
function arrayInsert(arr, index, value) {
    return [...arr.slice(0, index), value, ...arr.slice(index)];
}
function generatePoll(tweet, tweetElement, user) {
    let pollElement = tweetElement.getElementsByClassName('tweet-card')[0];
    pollElement.innerHTML = '';
    let poll = tweet.card.binding_values;
    let choices = Object.keys(poll).filter(key => key.endsWith('label')).map((key, i) => ({
        label: poll[key].string_value,
        count: poll[key.replace('label', 'count')] ? +poll[key.replace('label', 'count')].string_value : 0,
        id: parseInt(key.replace(/[^0-9]/g, ''))
    }));
    choices.sort((a, b) => a.id - b.id);
    let voteCount = choices.reduce((acc, cur) => acc + cur.count, 0);
    if(poll.selected_choice || user.id_str === tweet.user.id_str || (poll.counts_are_final && poll.counts_are_final.boolean_value)) {
        for(let i in choices) {
            let choice = choices[i];
            if(user.id_str !== tweet.user.id_str && poll.selected_choice && choice.id === +poll.selected_choice.string_value) {
                choice.selected = true;
            }
            choice.percentage = Math.round(choice.count / voteCount * 100) || 0;
            let choiceElement = document.createElement('div');
            choiceElement.classList.add('choice');
            choiceElement.innerHTML = html`
                <div class="choice-bg" style="width:${choice.percentage}%" data-percentage="${choice.percentage}"></div>
                <div class="choice-label">
                    <span>${escapeHTML(choice.label)}</span>
                    ${choice.selected ? `<span class="choice-selected"></span>` : ''}
                </div>
                ${isFinite(choice.percentage) ? `<div class="choice-count">${choice.count} (${choice.percentage}%)</div>` : '<div class="choice-count">0</div>'}
            `;
            pollElement.append(choiceElement);
        }
    } else {
        for(let i in choices) {
            let choice = choices[i];
            let choiceElement = document.createElement('div');
            choiceElement.classList.add('choice', 'choice-unselected');
            choiceElement.classList.add('tweet-button');
            choiceElement.innerHTML = html`
                <div class="choice-bg" style="width:100%"></div>
                <div class="choice-label">${escapeHTML(choice.label)}</div>
            `;
            choiceElement.addEventListener('click', async () => {
                let newCard = await API.tweet.vote(poll.api.string_value, tweet.id_str, tweet.card.url, tweet.card.name, choice.id);
                tweet.card = newCard.card;
                generateCard(tweet, tweetElement, user);
            });
            pollElement.append(choiceElement);
        }
    }
    if(tweet.card.url.startsWith('card://')) {
        let footer = document.createElement('span');
        footer.classList.add('poll-footer');
        let endsAtMessage = `Ends at: ${new Date(poll.end_datetime_utc.string_value).toLocaleString()}`;
        footer.innerHTML = html`${voteCount} ${voteCount === 1 ? 'vote' : 'votes'}${(!poll.counts_are_final || !poll.counts_are_final.boolean_value) && poll.end_datetime_utc ? ` ・ ${endsAtMessage}` : ''}`;
        pollElement.append(footer);
    }
}
function generateCard(tweet, tweetElement, user) {
    if(!tweet.card) return;
    if(tweet.card.name === 'promo_image_convo' || tweet.card.name === 'promo_video_convo') {
        let vals = tweet.card.binding_values;
        let a = document.createElement('a');
        a.title = vals.thank_you_text.string_value;
        if(tweet.card.name === 'promo_image_convo') {
            a.href = vals.thank_you_url ? vals.thank_you_url.string_value : "#";
            a.target = '_blank';
            let img = document.createElement('img');
            let imgValue = vals.promo_image;
            if(!imgValue) {
                imgValue = vals.cover_promo_image_original;
            }
            if(!imgValue) {
                imgValue = vals.cover_promo_image_large;
            }
            if(!imgValue) {
                return;
            }
            img.src = imgValue.image_value.url;
            let [w, h] = sizeFunctions[1](imgValue.image_value.width, imgValue.image_value.height);
            img.width = w;
            img.height = h;
            img.className = 'tweet-media-element';
            a.append(img);
        } else {
            let overlay = document.createElement('div');
            overlay.innerHTML = html`
                <svg viewBox="0 0 24 24" class="tweet-media-video-overlay-play">
                    <g>
                        <path class="svg-play-path" d="M8 5v14l11-7z"></path>
                        <path d="M0 0h24v24H0z" fill="none"></path>
                    </g>
                </svg>
            `;
            overlay.className = 'tweet-media-video-overlay';
            overlay.addEventListener('click', async e => {
                e.preventDefault();
                e.stopImmediatePropagation();
                try {
                    let res = await GM_fetch(vid.currentSrc); // weird problem with vids breaking cuz twitter sometimes doesnt send content-length
                    if(!res.headers.get('content-length')) await sleep(1000);
                } catch(e) {
                    console.error(e);
                }
                vid.play();
                vid.controls = true;
                vid.classList.remove('tweet-media-element-censor');
                overlay.style.display = 'none';
            });
            let vid = document.createElement('video');
            let [w, h] = sizeFunctions[1](vals.player_image_original.image_value.width, vals.player_image_original.image_value.height);
            vid.width = w;
            vid.height = h;
            vid.preload = 'none';
            vid.poster = vals.player_image_large.image_value.url;
            vid.className = 'tweet-media-element';
            vid.addEventListener('click', async e => {
                e.preventDefault();
                e.stopImmediatePropagation();
            });
            GM_fetch(vals.player_stream_url.string_value).then(res => res.text()).then(blob => {
                let xml = new DOMParser().parseFromString(blob, 'text/xml');
                let MediaFile = xml.getElementsByTagName('MediaFile')[0];
                vid.src = MediaFile.textContent.trim();
            });
            let tweetMedia = document.createElement('div');
            tweetMedia.className = 'tweet-media';
            tweetMedia.style.right = 'unset';
            tweetMedia.append(overlay, vid);
            a.append(tweetMedia);
        }
        let ctas = [];
        if(vals.cta_one) {
            ctas.push([vals.cta_one, vals.cta_one_tweet]);
        }
        if(vals.cta_two) {
            ctas.push([vals.cta_two, vals.cta_two_tweet]);
        }
        if(vals.cta_three) {
            ctas.push([vals.cta_three, vals.cta_three_tweet]);
        }
        if(vals.cta_four) {
            ctas.push([vals.cta_four, vals.cta_four_tweet]);
        }
    } else if(tweet.card.name === "player") {
        let iframe = document.createElement('iframe');
        iframe.src = tweet.card.binding_values.player_url.string_value.replace("youtube.com", "youtube-nocookie.com").replace("autoplay=true", "autoplay=false").replace("autoplay=1", "autoplay=0");
        iframe.classList.add('tweet-player');
        let [w, h] = sizeFunctions[1](+tweet.card.binding_values.player_width.string_value, +tweet.card.binding_values.player_height.string_value);
        iframe.width = w;
        iframe.height = h;
        iframe.loading = 'lazy';
        iframe.allowFullscreen = true;
        tweetElement.getElementsByClassName('tweet-card')[0].innerHTML = '';
        tweetElement.getElementsByClassName('tweet-card')[0].append(iframe);
    } else if(tweet.card.name === "unified_card") {
        let uc = JSON.parse(tweet.card.binding_values.unified_card.string_value);
        for(let cn of uc.components) {
            let co = uc.component_objects[cn];
            if(co.type === "media") {
                let media = uc.media_entities[co.data.id];

                if(media.type === "photo") {
                    let img = document.createElement('img');
                    img.className = 'tweet-media-element';
                    let [w, h] = sizeFunctions[1](media.original_info.width, media.original_info.height);
                    img.width = w;
                    img.height = h;
                    img.loading = 'lazy';
                    img.src = media.media_url_https;
                    img.addEventListener('click', () => {
                        new Viewer(img, {
                            transition: false,
                            zoomRatio: 0.3
                        });
                    });
                    tweetElement.getElementsByClassName('tweet-card')[0].append(img, document.createElement('br'));
                } else if(media.type === "animated_gif" || media.type === "video") {
                    let video = document.createElement('video');
                    video.className = 'tweet-media-element tweet-media-element-one';
                    let [w, h] = sizeFunctions[1](media.original_info.width, media.original_info.height);
                    video.width = w;
                    video.height = h;
                    video.crossOrigin = 'anonymous';
                    video.loading = 'lazy';
                    video.controls = true;
                    if(!media.video_info) {
                        console.log(`bug found in ${tweet.id_str}, please report this message to https://github.com/dimdenGD/OldTwitter/issues`, tweet);
                        continue;
                    };
                    let variants = media.video_info.variants.sort((a, b) => {
                        if(!b.bitrate) return -1;
                        return b.bitrate-a.bitrate;
                    });
                    for(let v in variants) {
                        let source = document.createElement('source');
                        source.src = variants[v].url;
                        source.type = variants[v].content_type;
                        video.append(source);
                    }
                    tweetElement.getElementsByClassName('tweet-card')[0].append(video, document.createElement('br'));
                }
            } else if(co.type === "app_store_details") {
                let app = uc.app_store_data[uc.destination_objects[co.data.destination].data.app_id][0];
                let appElement = document.createElement('div');
                appElement.classList.add('tweet-app-info');
                appElement.innerHTML = html`
                    <h3>${escapeHTML(app.title.content)}</h3>
                    <span>${escapeHTML(app.category.content)}</span>
                    <br>
                `;
                tweetElement.getElementsByClassName('tweet-card')[0].append(appElement);
            } else if(co.type === "button_group") {
                let buttonGroup = document.createElement('div');
                buttonGroup.classList.add('tweet-button-group');
                for(let b of co.data.buttons) {
                    let app = uc.app_store_data[uc.destination_objects[b.destination].data.app_id][0];
                    let button = document.createElement('a');
                    button.href = `http://play.google.com/store/apps/details?id=${app.id}`;
                    button.target = '_blank';
                    button.className = `nice-button tweet-app-button tweet-app-button-${b.style}`
                    button.innerText = b.action[0].toUpperCase() + b.action.slice(1);
                    buttonGroup.append(button);
                }
                tweetElement.getElementsByClassName('tweet-card')[0].append(buttonGroup);
            }
        }
    } else if(tweet.card.name === "summary" || tweet.card.name === "summary_large_image") {
        let vals = tweet.card.binding_values;
        let a = document.createElement('a');
        let url = vals.card_url.string_value;
        if(tweet.entities && tweet.entities.urls) {
            let urlEntity = tweet.entities.urls.find(u => u.url === url);
            if(urlEntity) {
                url = urlEntity.expanded_url;
            }
        }
        a.target = '_blank';
        a.href = url;
        a.className = 'tweet-card-link yeah-box';
        a.innerHTML = html`
            ${vals.thumbnail_image ? `<img src="${vals.thumbnail_image.image_value.url}" class="tweet-card-link-thumbnail">` : ''}
            <div class="tweet-card-link-text">
                ${vals.vanity_url ? `<span class="tweet-card-link-vanity">${escapeHTML(vals.vanity_url.string_value)}</span><br>` : ''}
                ${vals.title ? `<h3 class="tweet-card-link-title">${escapeHTML(vals.title.string_value)}</h3>` : ''}
                ${vals.description ? `<span class="tweet-card-link-description">${escapeHTML(vals.description.string_value)}</span>` : ''}
            </div>
        `;
        tweetElement.getElementsByClassName('tweet-card')[0].append(a);
    } else if(tweet.card.url.startsWith('card://')) {
        generatePoll(tweet, tweetElement, user);
    }
}
function createEmojiPicker(container, input, style = {}) {
    let picker = new EmojiPicker();
    for(let i in style) {
        picker.style[i] = style[i];
    }
    picker.className = isDarkModeEnabled ? 'dark' : 'light';
    picker.addEventListener('emoji-click', e => {
        let pos = input.selectionStart;
        let text = input.value;
        input.value = text.slice(0, pos) + e.detail.unicode + text.slice(pos);
        input.selectionStart = pos + e.detail.unicode.length;
    });
    container.append(picker);

    let observer;

    setTimeout(() => {
        function oc (e) {
            if (picker.contains(e.target)) return;
            if(observer) {
                observer.disconnect();
            }
            picker.remove();
            document.removeEventListener('click', oc);
            picker.database.close();
        }
        document.addEventListener('click', oc);
        picker.shadowRoot.querySelector("input.search").focus();
    }, 100);

    return picker;
}
function isEmojiOnly(str) {
    const stringToTest = str.replace(/ /g,'');
    const emojiRegex = /^(?:(?:\p{RI}\p{RI}|\p{Emoji}(?:\p{Emoji_Modifier}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?(?:\u{200D}\p{Emoji}(?:\p{Emoji_Modifier}|\u{FE0F}\u{20E3}?|[\u{E0020}-\u{E007E}]+\u{E007F})?)*)|[\u{1f900}-\u{1f9ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}])+$/u;
    return emojiRegex.test(stringToTest) && Number.isNaN(Number(stringToTest));
}

function renderMedia(t) {
    let _html = '';
    if(!t.extended_entities || !t.extended_entities.media) return '';

    let cws = [];

    for(let i = 0; i < t.extended_entities.media.length; i++) {
        let m = t.extended_entities.media[i];
        let toCensor = t.possibly_sensitive;
        if(m.type === 'photo') {
            let [w, h] = sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height);
            _html += html`
            <img 
                ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text.replaceAll('"', "'"))}" title="${escapeHTML(m.ext_alt_text.replaceAll('"', "'"))}"` : ''}
                crossorigin="anonymous"
                width="${w}"
                height="${h}"
                loading="lazy"
                src="${m.media_url_https + (false && (m.media_url_https.endsWith('.jpg') || m.media_url_https.endsWith('.png')) ? '?name=orig' : window.navigator && navigator.connection && navigator.connection.type === 'cellular' ? '?name=small' : '')}"
                class="tweet-media-element ${mediaClasses[t.extended_entities.media.length]} ${toCensor ? 'tweet-media-element-censor' : ''}"
            >`;
        } else if(m.type === 'animated_gif') {
            let [w, h] = sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height);
            let rid = m.id_str + m.media_key;
            _html += html`
                <video
                    ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
                    crossorigin="anonymous"
                    width="${w}"
                    height="${h}"
                    loop
                    disableRemotePlayback
                    autoplay
                    muted
                    class="tweet-media-element tweet-media-gif ${mediaClasses[t.extended_entities.media.length]} ${toCensor ? 'tweet-media-element-censor' : ''}"
                >
                    ${m.video_info.variants.map(v => `<source src="${v.url}" type="${v.content_type}">`).join('\n')}
                    Unsupported video
                </video>
            `;
        } else if(m.type === 'video') {
            if(m.mediaStats && m.mediaStats.viewCount) {
                m.ext = {
                    mediaStats: { r: { ok: { viewCount: m.mediaStats.viewCount } } }
                }
            }
            let [w, h] = sizeFunctions[t.extended_entities.media.length](m.original_info.width, m.original_info.height);
            _html += html`
                <video
                    ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''}
                    crossorigin="anonymous"
                    width="${w}"
                    height="${h}"
                    preload="none"
                    disableRemotePlayback
                    ${t.extended_entities.media.length > 1 ? 'controls' : ''}
                    poster="${m.media_url_https}"
                    class="tweet-media-element ${mediaClasses[t.extended_entities.media.length]} ${toCensor ? 'tweet-media-element-censor' : ''}"
                >
                    ${m.video_info.variants.map(v => `<source src="${v.url}" type="${v.content_type}">`).join('\n')}
                    Unsupported video
                </video>
            `;
        }
        if(i === 1 && t.extended_entities.media.length > 3) {
            _html += '<br>';
        }
    }

    if(cws.length > 0) {
        cws = [...new Set(cws)];
        cws = "Content warnings: " + cws.join(', ');
        _html += html`<br><div class="tweet-media-cws">${cws}</div>`;
    }
    return _html;
}


function openInNewTab(href) {
    Object.assign(document.createElement('a'), {
        target: '_blank',
        rel: 'noopener noreferrer',
        href: href,
    }).click();
}

function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function appendTweet(t, timelineContainer, options = {}, user) {
    if(typeof t !== 'object') {
        console.error('Tweet is undefined', t, timelineContainer, options);
        return;
    }
    if(typeof t.user !== 'object') {
        console.error('Tweet user is undefined', t, timelineContainer, options);
        return;
    }
    try {
        // verification
        if(t.user.ext_verified_type) {
            t.user.verified_type = t.user.ext_verified_type;
            t.user.verified = true;
        }
        if(t.user.ext && t.user.ext.isBlueVerified && t.user.ext.isBlueVerified.r && t.user.ext.isBlueVerified.r.ok) {
            t.user.verified_type = "Blue";
            t.user.verified = true;
        }
        if(t.user && t.user.ext && t.user.ext.verifiedType && t.user.ext.verifiedType.r && t.user.ext.verifiedType.r.ok) {
            t.user.verified_type = t.user.ext.verifiedType.r.ok;
            t.user.verified = true;
        }
        if(t.quoted_status && t.quoted_status.user.verified_type === "Blue") {
            delete t.quoted_status.user.verified_type;
            t.quoted_status.user.verified = false;
        }

        const tweet = document.createElement('div');
        tweet.tweet = t;
        t.element = tweet;
        t.options = options;

        if(!options.mainTweet) {
            tweet.addEventListener('click', e => {
                if(!e.target.closest(".tweet-button") && !e.target.closest(".tweet-body-text-span") && !e.target.closest(".tweet-edit-section") && !e.target.closest(".dropdown-menu") && !e.target.closest(".tweet-media-element") && !e.target.closest("a") && !e.target.closest("button")) {
                    let tweetData = t;
                    if(tweetData.retweeted_status) tweetData = tweetData.retweeted_status;
                    tweet.classList.add('tweet-preload');
                    let selection = window.getSelection();
                    if(selection.toString().length > 0 && selection.focusNode && selection.focusNode.closest(`div.tweet[data-tweet-id="${tweetData.id_str}"]`)) {
                        return;
                    }
                    let a = document.createElement('a');
                    a.href = `/${tweetData.user.screen_name}/status/${tweetData.id_str}`;
                    a.target = '_blank';
                    a.click();
                }
            });
        }
        tweet.addEventListener('mousedown', e => {
            if(e.button === 1) {
                // tweet-media-element is clickable, since it should open the tweet in a new tab.
                if(!e.target.closest(".tweet-button") && !e.target.closest(".tweet-edit-section") && !e.target.closest(".dropdown-menu") && !e.target.closest("a") && !e.target.closest("button")) {
                    e.preventDefault();
                    openInNewTab(`/${t.user.screen_name}/status/${t.id_str}`);
                }
            }
        });
        tweet.tabIndex = -1;
        tweet.className = `yeah-tweet ${options.mainTweet ? 'tweet-main' : location.pathname.includes('/status/') ? 'tweet-replying' : ''}`.trim();
        tweet.dataset.tweetId = t.id_str;
        tweet.dataset.userId = t.user.id_str;
        try {
            if(!activeTweet) {
                tweet.classList.add('tweet-active');
                activeTweet = tweet;
            }
        } catch(e) {};

        if(t.nonReply) {
            tweet.classList.add('tweet-non-reply');
        }

        if(t.threadContinuation) {
            options.threadContinuation = true;
        }
        if(t.noTop) {
            options.noTop = true;
        }
        if (options.threadContinuation) tweet.classList.add('tweet-self-thread-continuation');
        if (options.selfThreadContinuation) tweet.classList.add('tweet-self-thread-continuation');

        if (options.noTop) tweet.classList.add('tweet-no-top');
        let full_text = t.full_text ? t.full_text : '';
        let tweetLanguage = t.lang; // originally i used i18n api to detect languages simply because i didn't know of t.lang existence
        if(!tweetLanguage) {
            tweetLanguage = 'und';
        }
        if(tweetLanguage.includes('-')) {
            let [lang, country] = tweetLanguage.split('-');
            tweetLanguage = `${lang}_${country.toUpperCase()}`;
        }
        let videos = t.extended_entities && t.extended_entities.media && t.extended_entities.media.filter(m => m.type === 'video');
        if(!videos || videos.length === 0) {
            videos = undefined;
        }
        if(videos) {
            for(let v of videos) {
                if(!v.video_info) continue;
                v.video_info.variants = v.video_info.variants.sort((a, b) => {
                    if(!b.bitrate) return -1;
                    return b.bitrate-a.bitrate;
                });
            }
        }
        if(full_text.includes("Learn more")) {
            console.log(t);
        }
        if(t.withheld_in_countries && (t.withheld_in_countries.includes("XX") || t.withheld_in_countries.includes("XY"))) {
            full_text = "";
        }
        if(!t.quoted_status) { //t.quoted_status is undefined if the user blocked the quoter (this also applies to deleted/private tweets too, but it just results in original behavior then)
            try {
                if(t.quoted_status_result && t.quoted_status_result.result.tweet) {
                    t.quoted_status = t.quoted_status_result.result.tweet.legacy;
                    t.quoted_status.user = t.quoted_status_result.result.tweet.core.user_results.result.legacy;
                }/* else if(t.quoted_status_id_str) {
                    t.quoted_status = await API.tweet.getV2(t.quoted_status_id_str);
                    console.log(t.quoted_status);
                }*/
            } catch {
                t.quoted_status = undefined;
            }
        }
        let mentionedUserText = ``;
        let quoteMentionedUserText = ``;
        if(t.in_reply_to_screen_name && t.display_text_range) {
            t.entities.user_mentions.forEach(user_mention => {
                if(user_mention.indices[0] < t.display_text_range[0]){
                    mentionedUserText += `<a href="/${user_mention.screen_name}">@${user_mention.screen_name}</a> `
                }
                //else this is not reply but mention
            });
        }
        if(t.quoted_status && t.quoted_status.in_reply_to_screen_name && t.display_text_range) {
            t.quoted_status.entities.user_mentions.forEach(user_mention => {
                if(user_mention.indices[0] < t.display_text_range[0]){
                    quoteMentionedUserText += `@${user_mention.screen_name} `
                }
                //else this is not reply but mention
            });
        }
        // i fucking hate this thing
        tweet.innerHTML = html`
            <div class="tweet-top" hidden></div>
            <a class="tweet-avatar-link" href="/${t.user.screen_name}">
                <img
                    src="${`${t.user.profile_image_url_https}`.replace("_normal.", "_bigger.")}"
                    alt="${t.user.name}"
                    class="tweet-avatar"
                    width="48"
                    height="48"
                >
            </a>
            <div class="tweet-header ${options.mainTweet ? 'tweet-header-main' : ''}">
                <a class="tweet-header-info ${options.mainTweet ? 'tweet-header-info-main' : ''}" href="/${t.user.screen_name}">
                    <b
                        ${t.user.id_str === '1708130407663759360' ? 'title="Old Twitter Layout extension developer" ' : ''}
                        class="tweet-header-name ${options.mainTweet ? 'tweet-header-name-main' : ''} ${t.user.verified || t.user.verified_type ? 'user-verified' : t.user.id_str === '1708130407663759360' ? 'user-verified user-verified-dimden' : ''} ${t.user.protected ? 'user-protected' : ''} ${t.user.verified_type === 'Government' ? 'user-verified-gray' : t.user.verified_type === 'Business' ? 'user-verified-yellow' : t.user.verified_type === 'Blue' ? 'user-verified-blue' : ''}"
                    >${escapeHTML(t.user.name)}</b>
                    <span class="tweet-header-handle">@${t.user.screen_name}</span>
                </a>
                <a class="tweet-time" data-timestamp="${new Date(t.created_at).getTime()}" title="${new Date(t.created_at).toLocaleString()}" href="/${t.user.screen_name}/status/${t.id_str}">${timeElapsed(new Date(t.created_at).getTime())}</a>
            </div>
            <article class="tweet-body ${options.mainTweet ? 'tweet-body-main' : ''}">
                ${mentionedUserText !== `` &&
                    !options.threadContinuation &&
                    !options.noTop &&
                    !location.pathname.includes('/status/') ? html`
                <div class="tweet-reply-to"><span>${"Replying to $SCREEN_NAME$".replace('$SCREEN_NAME$', mentionedUserText.trim().replaceAll(`> <`, `>${", "}<`).replace(`>${", "}<`, `>${" and "}<`))}</span></div>
                `: ''}
                <div lang="${t.lang}" class="tweet-body-text tweet-body-text-long">
                    <span class="tweet-body-text-span">${full_text ? await renderTweetBodyHTML(t) : ''}</span>
                </div>
                ${t.extended_entities && t.extended_entities.media ? html`
                    <div class="tweet-media">
                        ${t.extended_entities.media.length === 1 && t.extended_entities.media[0].type === 'video' ? html`
                            <div class="tweet-media-video-overlay tweet-button">
                                <svg viewBox="0 0 24 24" class="tweet-media-video-overlay-play">
                                    <g>
                                        <path class="svg-play-path" d="M8 5v14l11-7z"></path>
                                        <path d="M0 0h24v24H0z" fill="none"></path>
                                    </g>
                                </svg>
                            </div>
                        ` : ''}
                        ${renderMedia(t)}
                    </div>
                    ${t.extended_entities && t.extended_entities.media && t.extended_entities.media.some(m => m.type === 'animated_gif') ? html`<div class="tweet-media-controls">GIF</div>` : ''}
                    <span class="tweet-media-data"></span>
                ` : ``}
                ${t.card ? `<div class="tweet-card"></div>` : ''}
                ${t.quoted_status ? html`
                <a class="tweet-body-quote" target="_blank" href="/${t.quoted_status.user.screen_name}/status/${t.quoted_status.id_str}">
                    <img src="${t.quoted_status.user.profile_image_url_https}" alt="${escapeHTML(t.quoted_status.user.name)}" class="tweet-avatar-quote" width="24" height="24">
                    <div class="tweet-header-quote">
                        <span class="tweet-header-info-quote">
                        <b class="tweet-header-name-quote ${t.quoted_status.user.verified ? 'user-verified' : t.quoted_status.user.id_str === '1708130407663759360' ? 'user-verified user-verified-dimden' : ''} ${t.quoted_status.user.protected ? 'user-protected' : ''} ${t.quoted_status.user.verified_type === 'Government' ? 'user-verified-gray' : t.quoted_status.user.verified_type === 'Business' ? 'user-verified-yellow' : t.quoted_status.user.verified_type === 'Blue' ? 'user-verified-blue' : ''}">${escapeHTML(t.quoted_status.user.name)}</b>
                        <span class="tweet-header-handle-quote">@${t.quoted_status.user.screen_name}</span>
                        </span>
                    </div>
                    <span class="tweet-time-quote" data-timestamp="${new Date(t.quoted_status.created_at).getTime()}" title="${new Date(t.quoted_status.created_at).toLocaleString()}">${timeElapsed(new Date(t.quoted_status.created_at).getTime())}</span>
                    ${quoteMentionedUserText !== `` ? html`
                    <span class="tweet-reply-to tweet-quote-reply-to">${"Replying to $SCREEN_NAME$".replace('$SCREEN_NAME$', quoteMentionedUserText.trim().replaceAll(` `,", ").replace(", "," and "))}</span>
                    ` : ''}
                    <span class="tweet-body-text tweet-body-text-quote tweet-body-text-long" style="color:var(--yeah-default-text-color)!important">${t.quoted_status.full_text ? await renderTweetBodyHTML(t, true) : ''}</span>
                    ${t.quoted_status.extended_entities && t.quoted_status.extended_entities.media ? html`
                    <div class="tweet-media-quote">
                        ${t.quoted_status.extended_entities.media.map(m => `<${m.type === 'photo' ? 'img' : 'video'} ${m.ext_alt_text ? `alt="${escapeHTML(m.ext_alt_text)}" title="${escapeHTML(m.ext_alt_text)}"` : ''} crossorigin="anonymous" width="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[0]}" height="${quoteSizeFunctions[t.quoted_status.extended_entities.media.length](m.original_info.width, m.original_info.height)[1]}" loading="lazy" ${m.type === 'video' ? 'disableRemotePlayback controls' : ''} ${m.type === 'animated_gif' ? 'disableRemotePlayback loop muted onclick="if(this.paused) this.play(); else this.pause()"' : ''}${m.type === 'animated_gif' ? ' autoplay' : ''} src="${m.type === 'photo' ? m.media_url_https + (false && (m.media_url_https.endsWith('.jpg') || m.media_url_https.endsWith('.png')) ? '?name=orig' : window.navigator && navigator.connection && navigator.connection.type === 'cellular' ? '?name=small' : '') : m.video_info.variants.find(v => v.content_type === 'video/mp4').url}" class="tweet-media-element tweet-media-element-quote ${m.type === 'animated_gif' ? 'tweet-media-element-quote-gif' : ''} ${mediaClasses[t.quoted_status.extended_entities.media.length]}">${m.type === 'photo' ? '' : '</video>'}`).join('\n')}
                    </div>
                    ` : ''}
                </a>
                ` : ``}
                ${t.limited_actions === 'limit_trusted_friends_tweet' && (options.mainTweet || !location.pathname.includes('/status/')) ? html`
                <div class="tweet-limited">
                    ${"This tweet is visible only to people who are in @$SCREEN_NAME$'s trusted friends circle."}
                    <a href="https://help.twitter.com/en/using-twitter/twitter-circle" target="_blank">${"Learn more."}</a>
                </div>
                `.replace('$SCREEN_NAME$', tweet.trusted_circle_owner ? tweet.trusted_circle_owner : tweetStorage[t.conversation_id_str] ? tweetStorage[t.conversation_id_str].user.screen_name : t.in_reply_to_screen_name ? t.in_reply_to_screen_name : t.user.screen_name) : ''}
                ${t.tombstone ? `<div class="tweet-warning">${t.tombstone}</div>` : ''}
                <a ${!options.mainTweet ? 'hidden' : ''} class="tweet-date" title="${new Date(t.created_at).toLocaleString()}" href="/${t.user.screen_name}/status/${t.id_str}"><br>${new Date(t.created_at).toLocaleTimeString(undefined, { hour: 'numeric', minute: 'numeric' }).toLowerCase()} - ${new Date(t.created_at).toLocaleDateString(undefined, { day: 'numeric', month: 'short', year: 'numeric' })}  ・ ${t.source ? t.source.split('>')[1].split('<')[0] : 'Unknown'}</a>
                <div class="tweet-interact">
                    <span class="tweet-button tweet-interact-reply" title="Replies" data-val="${t.reply_count}">${options.mainTweet ? '' : formatLargeNumber(t.reply_count).replace(/\s/g, ',')}</span>
                    <span title="Retweets" class="tweet-button tweet-interact-retweet${t.retweeted ? ' tweet-interact-retweeted' : ''}${(t.user.protected || t.limited_actions === 'limit_trusted_friends_tweet') && t.user.id_str !== user.id_str ? ' tweet-interact-retweet-disabled' : ''}" data-val="${t.retweet_count}">${options.mainTweet ? '' : formatLargeNumber(t.retweet_count).replace(/\s/g, ',')}</span>
                    <span title="Likes" class="tweet-button tweet-yeah-interact-favorite ${t.favorited ? 'tweet-yeah-interact-favorited' : ''}" data-val="${t.favorite_count}">${options.mainTweet ? '' : formatLargeNumber(t.favorite_count).replace(/\s/g, ',')}</span>
                    ${t.ext && t.ext.views && t.ext.views.r && t.ext.views.r.ok && t.ext.views.r.ok.count ? html`<span title="${"Views"}" class="tweet-interact-views tweet-button" data-val="${t.ext.views.r.ok.count}">${formatLargeNumber(t.ext.views.r.ok.count).replace(/\s/g, ',')}</span>` : ''}
                </div>
            </article>
        `;
        // gifs
        let gifs = Array.from(tweet.querySelectorAll('.tweet-media-gif, .tweet-media-element-quote-gif'));
        if(gifs.length) {
            gifs.forEach(gif => {
                gif.addEventListener('click', () => {
                    if(gif.paused) gif.play();
                    else gif.pause();
                });
            });
        }
        // video
        let vidOverlay = tweet.getElementsByClassName('tweet-media-video-overlay')[0];
        if(vidOverlay) {
            vidOverlay.addEventListener('click', async () => {
                let vid = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO')[0];
                try {
                    let res = await GM_fetch(vid.currentSrc); // weird problem with vids breaking cuz twitter sometimes doesnt send content-length
                    if(!res.headers.get('content-length')) await sleep(1000);
                } catch(e) {
                    console.error(e);
                }
                vid.play();
                vid.controls = true;
                vid.classList.remove('tweet-media-element-censor');
                vidOverlay.style.display = 'none';
            });
        }
        if(videos) {
            let videoErrors = 0;
            let vids = Array.from(tweet.getElementsByClassName('tweet-media')[0].children).filter(e => e.tagName === 'VIDEO');
            vids[0].addEventListener('error', () => {
                if(videoErrors >= 3) return;
                videoErrors++;
                setTimeout(() => {
                    vids[0].load();
                }, 25);
            })
            for(let vid of vids) {
                vid.addEventListener('mousedown', e => {
                    if(e.button === 1) {
                        e.preventDefault();
                        window.open(vid.currentSrc, '_blank');
                    }
                });
            }
        }

        if(t.card) {
            generateCard(t, tweet, user);
        }
        if (options.top) {
            tweet.querySelector('.tweet-top').hidden = false;
            const icon = document.createElement('span');
            icon.innerText = options.top.icon;
            icon.classList.add('tweet-top-icon');
            icon.style.color = options.top.color;

            const span = document.createElement("span");
            span.classList.add("tweet-top-text");
            span.innerHTML = options.top.text;
            if(options.top.class) {
                span.classList.add(options.top.class);
                tweet.classList.add(`tweet-top-${options.top.class}`);
            }
            tweet.querySelector('.tweet-top').append(icon, span);
        }

        const tweetBodyQuote = tweet.getElementsByClassName('tweet-body-quote')[0];
        const tweetMediaQuote = tweet.getElementsByClassName('tweet-media-quote')[0];
        const tweetInteract = tweet.getElementsByClassName('tweet-interact')[0];
        const tweetFooter = tweet.getElementsByClassName('tweet-footer')[0];

        // community notes
        if(t.birdwatch) {
            if(t.birdwatch.subtitle) {
                let div = document.createElement('div');
                div.classList.add('tweet-birdwatch', 'box');
                let text = Array.from(escapeHTML(t.birdwatch.subtitle.text));
                for(let e = t.birdwatch.subtitle.entities.length - 1; e >= 0; e--) {
                    let entity = t.birdwatch.subtitle.entities[e];
                    if(!entity.ref) continue;
                    text = arrayInsert(text, entity.toIndex, '</a>');
                    text = arrayInsert(text, entity.fromIndex, `<a href="${entity.ref.url}" target="_blank">`);
                }
                text = text.join('');
                
                div.innerHTML = html`
                    <div class="tweet-birdwatch-header">
                        <span class="tweet-birdwatch-title">${escapeHTML(t.birdwatch.title)}</span>
                    </div>
                    <div class="tweet-birdwatch-body">
                        <span class="tweet-birdwatch-subtitle">${text}</span>
                    </div>
                `;
    
                if(tweetFooter) tweetFooter.before(div);
                else tweetInteract.before(div);
            }
        }

        // Quote body
        if(tweetMediaQuote) tweetMediaQuote.addEventListener('click', e => {
            if(e && e.target && e.target.tagName === "VIDEO") {
                e.preventDefault();
                e.stopPropagation();
                e.stopImmediatePropagation();
                if(e.target.paused) {
                    e.target.play();
                } else {
                    e.target.pause();
                }
            }
        });
        if(tweetBodyQuote) {
            tweetBodyQuote.addEventListener('click', e => {
                e.preventDefault();
                let a = document.createElement('a');
                a.href = `/${t.quoted_status.user.screen_name}/status/${t.quoted_status.id_str}`;
                a.target = '_blank';
                a.click();
            });
        }

        // Media
        if (t.extended_entities && t.extended_entities.media) {
            const tweetMedia = tweet.getElementsByClassName('tweet-media')[0];
            tweetMedia.addEventListener('click', e => {
                if (e.target.className && e.target.className.includes('tweet-media-element-censor')) {
                    return e.target.classList.remove('tweet-media-element-censor');
                }
                if (e.target.tagName === 'IMG') {
                    if(!e.target.src.includes('?name=') && !e.target.src.endsWith(':orig') && !e.target.src.startsWith('data:')) {
                        e.target.src += '?name=orig';
                    } else if(e.target.src.includes('?name=small')) {
                        e.target.src = e.target.src.replace('?name=small', '?name=large');
                    }
                    new Viewer(tweetMedia, {
                        transition: false,
                        zoomRatio: 0.3
                    });
                    e.target.click();
                }
            });
        }

        if(options.noInsert) {
            return tweet;
        }

        if(options.after) {
            options.after.after(tweet);
        } else if (options.before) {
            options.before.before(tweet);
        } else if (options.prepend) {
            timelineContainer.prepend(tweet);
        } else {
            timelineContainer.append(tweet);
        }
        return tweet;
    } catch(e) {
        console.error(e);
        if(Date.now() - lastTweetErrorDate > 1000) {
            lastTweetErrorDate = Date.now();
            createModal(/*html*/`
                <div style="max-width:700px">
                    <span style="font-size:14px;color:var(--default-text-color)">
                        <h2 style="margin-top: 0">Something went wrong</h2>
                        Some tweets couldn't be loaded due to errors.<br>
                        ${"Please copy text below and send it to $AT1$issue tracker$AT2$ or $AT3$my email$AT2$. Thank you!".replace('$AT1$', "<a target='_blank' href='https://github.com/dimdenGD/YeahTwitter/issues'>").replace(/\$AT2\$/g, '</a>').replace("$AT3$", "<a target='_blank' href='mailto:admin@dimden.dev'>")}
                    </span>
                    <div class="box" style="font-family:monospace;line-break: anywhere;padding:5px;margin-top:5px;background:rgba(255, 0, 0, 0.1);color:#ff4545">
                        ${escapeHTML(e.stack ? e.stack : String(e))} at ${t.id_str} (YeahTwitter v${chrome.runtime.getManifest().version})
                    </div>
                </div>
            `);
        }
        return null;
    }
}

// scripts/content.js
const API_URL = `https://yeah.dimden.dev/api`;

Promise.all([
    GM_fetch('https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/styles/style.css').then(res => res.text()),
    GM_fetch('https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/styles/tweet.css').then(res => res.text())
]).then(styles => {
    setTimeout(() => {
        for(let css of styles) {
            let style = document.createElement('style');
            let head = document.head || document.getElementsByTagName('head')[0];
            let isFirefox = navigator.userAgent.indexOf('Firefox') > -1;
            if(isFirefox) css = css.replaceAll('chrome-extension://', 'moz-extension://');
            style.innerHTML = css.replaceAll('__MSG_@@extension_id__', chrome.runtime.id);
            head.appendChild(style);
        }
    }, 750);
});

setTimeout(async () => {
    let yeahToken = await getYeahToken();
    let ignorePopup = await new Promise(resolve => chrome.storage.local.get('ignorePopup', result => resolve(result.ignorePopup)));
    let userId = await getUserId();
    if(!yeahToken && ignorePopup && ignorePopup[userId]) {
        return;
    }
    if(!yeahToken) {
            let modalOpenTime = Date.now();
            let modal = createModal(/*html*/`
                <h2 style="margin-top:0">
                    <img src="${YEAH_images['yeah_on32.png']}" alt="Yeah!" style="width: 24px; height: 24px;margin-bottom: -4px;">
                    Welcome to Yeah! for Twitter extension!
                </h2>
                <p>This extension adds a <b>Yeah!</b> button to all tweets, which is essentially same thing as a Like but public to everyone. Everyone can see who Yeahed a tweet, and everyone can see all your Yeahs on your profile.</p>
                <p>It doesn't send a spammy reply with an image, instead it saves your Yeahs into a shared database.</p>
                <p>
                    In order to get started, you need to authenticate your Twitter account.
                    Click button below, and we'll automatically post a tweet on your behalf that will look like 'yeah-xxxxxxxx'.
                    Then our server will check for that tweet existence, confirm that it's you, and extension will automatically remove the tweet and save your token.
                    This tweet should be only up for about a second, so don't worry about posting nonsensical tweet.
                </p>
                <p>
                    <b>Important: your account must not be private so server can actually see the tweet. You'll need to make your account public for this auth, afterwards you can make it private again.</b>
                </p>
                <div class="error-message"></div>
                <div>
                    <button class="auth-button nice-yeah-button">Authenticate</button>
                </div>
                <div style="margin-top: 10px">
                    <span class="subtle dontshow" role="button">Never show this popup for this account</span>
                </div>
            `, 'welcome-modal', () => {}, () => Date.now() - modalOpenTime > 1250);
        
            let button = modal.querySelector('.auth-button');
            button.addEventListener('click', async () => {
                button.disabled = true;
                button.textContent = 'Authenticating...';
                let tweetId;
                try {
                    // get tokens
                    let tokens = JSON.parse(await callYeahApi('/request_token'));
        
                    // create tweet
                    let tweet = await callTwitterApi('POST', '/graphql/oB-5XsHNAbjvARJEc8CZFw/CreateTweet', {}, {
                        "variables":{"tweet_text": `yeah-${tokens.public_token}`,"dark_request":false,"media":{"media_entities":[],"possibly_sensitive":false},"semantic_annotation_ids":[]},
                        "features":{"communities_web_enable_tweet_community_results_fetch":true,"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":true,"tweet_awards_web_tipping_enabled":false,"creator_subscriptions_quote_tweet_preview_enabled":false,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"articles_preview_enabled":true,"rweb_video_timestamps_enabled":true,"rweb_tipjar_consumption_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_enhance_cards_enabled":false},
                        "queryId":"oB-5XsHNAbjvARJEc8CZFw"
                    });
        
                    // parse tweet
                    let tweetResult = tweet.data.create_tweet.tweet_results.result;
                    let tweetData = tweetResult.legacy;
                    tweetData.user = tweetResult.core.user_results.result.legacy;
                    tweetData.user.id_str = tweetData.user_id_str;
                    tweetId = tweetData.id_str;
        
                    // send tweet
                    let res = await callYeahApi('/verify_token', {
                        tweet: tweetData,
                        public_token: tokens.public_token,
                        private_token: tokens.private_token
                    });
                    if(res === 'success') {
                        chrome.storage.local.get('yeahTokens', result => {
                            if(!result.yeahTokens) result.yeahTokens = {};
                            result.yeahTokens[userId] = tokens.private_token;
                            chrome.storage.local.set(result);
                        });
                        modal.removeModal();
        
                        modalOpenTime = Date.now();
                        let modal2 = createModal(/*html*/`
                            <h2 style="margin-top:0">
                                <img src="${YEAH_images['yeah_on32.png']}" alt="Yeah!" style="width: 24px; height: 24px;margin-bottom: -4px;">
                                Authentification successful!
                            </h2>
                            <p>You can now Yeah! on any tweet. Yeah!!!!!</p>
                            <div>
                                btw I (<a href="/d1mden" target="_blank" style="text-decoration:none;color:#1d9bf0">@d1mden</a>) make a lot of cool extensions for Twitter like this, maybe u wanna follow me?
                            </div>
                            <div style="margin-top: 10px;"><button class="follow-button nice-yeah-button">Yeah! (Follow)</button></div>
                        `, 'authentification-successful', () => {}, () => Date.now() - modalOpenTime > 1500);
        
                        let followButton = modal2.querySelector('.follow-button');
                        followButton.addEventListener('click', () => {
                            callTwitterApi('POST', '/1.1/friendships/create.json', {
                                "Content-Type": "application/x-www-form-urlencoded"
                            }, {
                                include_profile_interstitial_type: 1,
                                include_blocking: 1,
                                include_blocked_by: 1,
                                include_followed_by: 1,
                                include_want_retweets: 1,
                                include_mute_edge: 1,
                                include_can_dm: 1,
                                include_can_media_tag: 1,
                                include_ext_is_blue_verified: 1,
                                include_ext_verified_type: 1,
                                include_ext_profile_image_shape: 1,
                                skip_status: 1,
                                user_id: "1708130407663759360"
                            }).then(() => {
                                modal2.removeModal();
                                alert('Thank you! Happy Yeahing!');
                            }).catch(e => {
                                console.error(e);
                                location.href = '/d1mden';
                            });
                        });
                    } else {
                        throw new Error(res);
                    }
                } catch(e) {
                    console.error(e);
                    modal.querySelector('.error-message').innerHTML = `Failed to authenticate. Please try again later. (${e.message})`;
                } finally {
                    button.disabled = false;
                    button.textContent = 'Authenticate';
                    if(tweetId) {
                        callTwitterApi('POST', `/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet`, {}, {
                            variables: {tweet_id: tweetId, dark_request: false},
                            queryId: "VaenaVgh5q5ih7kvyVjgtg"
                        });
                    }
                }
            });

            let dontshow  = modal.querySelector('.dontshow');
            dontshow.addEventListener('click', () => {
                chrome.storage.local.get('ignorePopup', result => {
                    if(!result.ignorePopup) result.ignorePopup = {};
                    result.ignorePopup[userId] = true;
                    chrome.storage.local.set(result);
                    modal.removeModal();
                    alert('Popup will not show again for this account. If you want to show it again, press on extension icon and press "Reset popup settings".');
                });
            });
        };
}, 1000);

let fetchQueue = [];
function hookIntoTweets() {
    let tweets = document.getElementsByTagName('article');

    for (let i = 0; i < tweets.length; i++) {
        let tweet = tweets[i];
        if(tweet.dataset.yeahed) continue;
        tweet.dataset.yeahed = true;

        let linkToTweet = Array.from(tweet.querySelectorAll('a[role="link"]')).find(a => a.href.includes('/status/') && !a.href.includes('/photo') && !a.href.includes('/video'));
        let oldTwitter = false;
        if(!linkToTweet) {
            let tweetDiv = tweet.closest('.tweet, .yeah-tweet');
            if(tweetDiv) {
                oldTwitter = true;
                linkToTweet = tweetDiv.querySelector('.tweet-time');
            } else {
                continue;
            }
        };
        let id = linkToTweet.href.match(/\/status\/(\d+)/)[1];
        if(!id) continue;

        fetchQueue.push(id);

        let div = document.createElement('div');
        let button = document.createElement('button');
        button.dataset.count = tweetCache[id] ? tweetCache[id].count : 0;
        button.addEventListener('click', async () => {
            if(!await getYeahToken()) {
                return alert('You need to authenticate first (refresh page for auth popup to appear)');
            }
            if(!button.classList.contains('yeahed')) {
                callYeahApi('/yeah', {
                    post_id: id
                });
                button.querySelector('.yeah-image').src = YEAH_images['yeah_on32.png'];
                let yeahCounter = button.querySelector('.yeah-counter');
                let count = parseInt(button.dataset.count);
                yeahCounter.innerText = formatLargeNumber(count + 1);
                button.dataset.count = count + 1;
                button.classList.add('yeahed');
                if(tweetCache[id]) {
                    tweetCache[id].yeahed = true;
                    tweetCache[id].count++;
                }
                let likeButton = tweet.querySelector('button[data-testid="like"], .tweet-interact-favorite:not(.tweet-interact-favorited)');
                if(likeButton) {
                    let settings = await getYeahSettings();
                    if(!settings.dontLike) likeButton.click();
                }
            } else {
                callYeahApi('/unyeah', {
                    post_id: id
                });
                button.classList.remove('yeahed');
                let yeahCounter = button.querySelector('.yeah-counter');
                let count = parseInt(button.dataset.count);
                yeahCounter.innerText = formatLargeNumber(count - 1);
                button.dataset.count = count - 1;
                if(count - 1 <= 0) yeahCounter.innerText = '';
                if(tweetCache[id]) {
                    tweetCache[id].yeahed = false;
                    tweetCache[id].count--;
                    if(tweetCache[id].count < 0) tweetCache[id].count = 0;
                }
                let likeButton = tweet.querySelector('button[data-testid="unlike"], .tweet-interact-favorite.tweet-interact-favorited');
                if(likeButton) {
                    let settings = await getYeahSettings();
                    if(!settings.dontLike) likeButton.click();
                }
            }
        });
        button.addEventListener('mouseover', () => {
            button.querySelector('.yeah-image').src = YEAH_images['yeah_on32.png'];
        });
        button.addEventListener('mouseout', () => {
            if(!button.classList.contains('yeahed')) button.querySelector('.yeah-image').src = YEAH_images['yeah_off32.png'];
        });
        button.className = `yeah-button yeah-button-${id}`;
        div.className = 'yeah-button-container';

        let img = document.createElement('img');
        img.src = tweetCache[id] && tweetCache[id].yeahed ? YEAH_images['yeah_on32.png'] : YEAH_images['yeah_off32.png'];
        if(tweetCache[id] && tweetCache[id].yeahed) button.classList.add('yeahed');
        img.className = 'yeah-image';
        img.draggable = false;
        button.appendChild(img);

        let counter = document.createElement('span');
        counter.className = 'yeah-counter';
        counter.innerText = tweetCache[id] && typeof tweetCache[id].count === 'number' ? formatLargeNumber(tweetCache[id].count) : '';
        if(oldTwitter) {
            counter.classList.add('yeah-counter-oldtwitter');
        }
        
        button.appendChild(counter);
        div.appendChild(button);

        let group = tweet.querySelector('div[role="group"]');
        if(group && group.children && group.children[3]) group.children[3].after(div);
        else {
            let interactButton = tweet.querySelector('.tweet-interact-favorite, .tweet-yeah-interact-favorite');
            if(interactButton) {
                div.classList.add('yeah-button-container-oldtwitter');
                interactButton.after(div);
            }
        }
    }
}

function updateButton(data) {
    if(!data) return;
    let buttons = Array.from(document.getElementsByClassName(`yeah-button-${data.post_id}`));
    for(let button of buttons) {
        if(data.yeahed) {
            button.classList.add('yeahed');
            button.querySelector('.yeah-image').src = YEAH_images['yeah_on32.png'];
        } else {
            button.classList.remove('yeahed');
            button.querySelector('.yeah-image').src = YEAH_images['yeah_off32.png'];
        }
        button.dataset.count = data.count;

        let counter = button.querySelector('.yeah-counter');
        counter.innerText = data.count === 0 ? '' : formatLargeNumber(data.count);
    }
}

let tweetCache = {};
setInterval(() => tweetCache = {}, 1000 * 60 * 5);
setInterval(async () => {
    if(fetchQueue.length > 0 && await getYeahToken()) {
        let first100 = fetchQueue.splice(0, 100);
        let cachedData = first100.map(id => tweetCache[id]).filter(Boolean);
        for(let cache of cachedData) {
            updateButton(cache);
        }
        first100 = first100.filter((id) => !tweetCache[id]);
        if(!first100.length) return;
        for(let id of first100) {
            tweetCache[id] = {
                post_id: id,
                yeahed: false,
                count: 0
            };
        }
        let data = JSON.parse(await callYeahApi('/get', {
            post_ids: first100.join(',')
        }));
        for(let i in data) {
            tweetCache[data[i].post_id] = data[i];
            updateButton(data[i]);
        }
    }
}, 1500);

function hookIntoInteractions() {
    let path = window.location.pathname;
    let addedTab;
    if(path.includes('/status/') && (path.endsWith('/quotes') || path.endsWith('/retweets') || path.endsWith('/likes'))) {
        let tablist = document.querySelector('div[role="tablist"]');
        if(!tablist) return;
        if(tablist.dataset.yeahed) return;
        tablist.dataset.yeahed = true;

        let yeahTab = document.createElement('div');
        yeahTab.className = 'yeah-tab';

        let span = document.createElement('span');
        span.innerText = 'Yeahs';
        yeahTab.appendChild(span);
        tablist.appendChild(yeahTab);

        addedTab = yeahTab;
    } else {
        let tablist = document.querySelector('.tweet-footer-stats');
        if(!tablist) return;
        if(tablist.dataset.yeahed) return;
        tablist.dataset.yeahed = true;

        let yeahTab = document.createElement('a');
        yeahTab.className = 'tweet-footer-stat';
        yeahTab.style.cursor = 'pointer';

        let span = document.createElement('span');
        span.innerText = 'Yeahs';
        span.className = 'tweet-footer-stat-text';

        let b = document.createElement('b');
        let id = location.pathname.match(/\/status\/(\d+)/)[1];
        b.innerText = tweetCache[id] && typeof tweetCache[id].count === 'number' ? formatLargeNumber(tweetCache[id].count) : '?';
        b.className = 'tweet-footer-stat-count';

        yeahTab.appendChild(span);
        yeahTab.appendChild(b);
        tablist.appendChild(yeahTab);

        addedTab = yeahTab;
    }

    if(addedTab) {
        addedTab.addEventListener('click', async() => {
            if(!await getYeahToken()) {
                return alert('You need to authenticate first (refresh page for auth popup to appear)');
            }
            let modal = createModal(/*html*/`
                <h3>Yeahs</h3>
                <div class="list"></div>
                <div class="loader" style="text-align:center">
                    <img src="${YEAH_images['loading.svg']}" width="64" height="64">
                </div>
            `, 'yeah-users');

            let list = modal.querySelector('.list');

            let data = JSON.parse(await callYeahApi('/get_users', {
                post_id: path.match(/\/status\/(\d+)/)[1],
                page: 1
            }));

            if(!data.length) {
                modal.querySelector('.loader').hidden = true;
                list.innerHTML = 'No Yeahs yet';
                return;
            }

            let lookup = await API.user.lookup(data);

            modal.querySelector('.loader').hidden = true;

            let addedUsers = [];
            
            for(let id of data) {
                let user = lookup.find(user => user.id_str === id);
                if(user) {
                    appendUser(user, list);
                    addedUsers.push(user.id_str);
                }
            }

            let modalContent = modal.querySelector('.yeah-modal-content');
            let over = false, loadingMore = false, page = 2;
            modalContent.addEventListener('scroll', async () => {
                if(over) return;
                if(loadingMore) return;

                let scrollPosition = modalContent.scrollTop + modalContent.offsetHeight;
                if(scrollPosition >= modalContent.scrollHeight - 200) {
                    loadingMore = true;
                    modal.querySelector('.loader').hidden = false;
                    let data = JSON.parse(await callYeahApi('/get_users', {
                        post_id: path.match(/\/status\/(\d+)/)[1],
                        page: page++
                    }));
                    if(!data.length) {
                        over = true;
                        modal.querySelector('.loader').hidden = true;
                        return;
                    }
                    let lookup = await API.user.lookup(data);
                    for(let id of data) {
                        if(addedUsers.includes(id)) continue;

                        let user = lookup.find(user => user.id_str === id);
                        if(user) {
                            appendUser(user, list);
                            addedUsers.push(user.id_str);
                        }
                    }
                    loadingMore = false;
                    modal.querySelector('.loader').hidden = true;
                }
            });
        });
    }
}

function hookIntoProfile() {
    if(['/notifications', '/explore', '/home', '/messages', '/compose'].includes(window.location.pathname)) return;
    if(window.location.pathname.startsWith('/search')) return;
    if(window.location.pathname.startsWith('/i/')) return;
    if(window.location.pathname.startsWith('/explore/')) return;
    if(window.location.pathname.startsWith('/notifications/')) return;
    if(window.location.pathname.startsWith('/compose/')) return;
    if(window.location.pathname.startsWith('/messages/')) return;
    if(window.location.pathname.includes('/communities/')) return;
    if(window.location.pathname.includes('/status/')) return;

    let addedTab;
    let profileStats = document.querySelector('#profile-stats');
    if(!profileStats) {
        let tablist = document.querySelector('div:not([data-testid="toolBar"]) > nav[role="navigation"][aria-live="polite"] div div[role="tablist"]');
        if(!tablist) return;
        if(tablist.dataset.yeahed) return;
        tablist.dataset.yeahed = true;
    
        let yeahTab = document.createElement('div');
        yeahTab.className = 'yeah-tab';
        let span = document.createElement('span');
        span.innerText = 'Yeahs';

        yeahTab.appendChild(span);   
        tablist.appendChild(yeahTab);
        
        addedTab = yeahTab;
    } else {
        if(profileStats.dataset.yeahed) return;
        profileStats.dataset.yeahed = true;

        let yeahTab = document.createElement('a');
        yeahTab.className = 'profile-stat';
        yeahTab.style.cursor = 'pointer';

        let span = document.createElement('span');
        span.innerText = 'Yeahs';
        span.className = 'profile-stat-text';

        let span2 = document.createElement('span');
        span2.className = 'profile-stat-value';
        span2.innerText = '?';

        setTimeout(() => {
            let avatar = document.getElementById('profile-avatar');
            if(!avatar || !avatar.dataset.user_id) return;
            let id = avatar.dataset.user_id;
            callYeahApi('/get_user_yeah_count', {
                user_id: id
            }).then(data => {
                data = JSON.parse(data);
                if(typeof data.count === 'number') span2.innerText = formatLargeNumber(data.count);
            });
        }, 2000);

        yeahTab.appendChild(span);
        yeahTab.appendChild(span2);

        profileStats.appendChild(yeahTab);

        addedTab = yeahTab;
    }
    if(addedTab) addedTab.addEventListener('click', async () => {
        if(!await getYeahToken()) {
            return alert('You need to authenticate first (refresh page for auth popup to appear)');
        }
        let username = window.location.pathname.split('/')[1];
        let modal = createModal(/*html*/`
            <h3>${username}'s Yeahs</h3>
            <div class="list"></div>
            <div class="loader" style="text-align:center">
                <img src="${YEAH_images['loading.svg']}" width="64" height="64">
            </div>
        `, 'yeah-posts');

        let list = modal.querySelector('.list');
        let user = await API.user.get(username, false);

        let data = JSON.parse(await callYeahApi('/get_yeahs', {
            user_id: user.id_str,
            page: 1
        }));

        if(!data.length) {
            modal.querySelector('.loader').hidden = true;
            list.innerHTML = 'No Yeahs yet';
            return;
        }

        let tweets = await API.tweet.lookup(data);

        if(!tweets.length) {
            modal.querySelector('.loader').hidden = true;
            list.innerHTML = 'No Yeahs yet';
            return;
        }

        
        let addedPosts = [];
        for(let id of data) {
            let tweet = tweets.find(tweet => tweet.id_str === id);
            if(tweet) {
                await appendTweet(tweet, list, {}, user);
                addedPosts.push(tweet.id_str);
            }
        }
        modal.querySelector('.loader').hidden = true;

        let modalContent = modal.querySelector('.yeah-modal-content');
        let over = false, loadingMore = false, page = 2;
        modalContent.addEventListener('scroll', async () => {
            if(over) return;
            if(loadingMore) return;

            let scrollPosition = modalContent.scrollTop + modalContent.offsetHeight;
            if(scrollPosition >= modalContent.scrollHeight - 200) {
                loadingMore = true;
                modal.querySelector('.loader').hidden = false;
                let data = JSON.parse(await callYeahApi('/get_yeahs', {
                    user_id: user.id_str,
                    page: page++
                }));
                if(!data.length) {
                    over = true;
                    modal.querySelector('.loader').hidden = true;
                    return;
                }
                let tweets = await API.tweet.lookup(data);
                for(let id of data) {
                    if(addedPosts.includes(id)) continue;
                    let tweet = tweets.find(tweet => tweet.id_str === id);
                    if(tweet) {
                        await appendTweet(tweet, list, {}, user);
                        addedPosts.push(tweet.id_str);
                    }
                }
                loadingMore = false;
                modal.querySelector('.loader').hidden = true;
            }
        });
    });
}

setInterval(hookIntoTweets, 250);
setInterval(hookIntoInteractions, 500);
setInterval(hookIntoProfile, 500);