您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds Yeah! button to Twitter, essentially a public Like
// ==UserScript== // @name Yeah! for Twitter // @namespace http://tampermonkey.net/ // @version 1.1.4 // @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,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' 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": "Bearer AAAAAAAAAAAAAAAAAAAAAG5LOQEAAAAAbEKsIYYIhrfOQqm4H8u7xcahRkU%3Dz98HKmzbeXdKqBfUDmElcqYl0cmmKY9KdS2UoNIz3Phapgsowi", "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": "Bearer AAAAAAAAAAAAAAAAAAAAAG5LOQEAAAAAbEKsIYYIhrfOQqm4H8u7xcahRkU%3Dz98HKmzbeXdKqBfUDmElcqYl0cmmKY9KdS2UoNIz3Phapgsowi", "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": "Bearer AAAAAAAAAAAAAAAAAAAAAG5LOQEAAAAAbEKsIYYIhrfOQqm4H8u7xcahRkU%3Dz98HKmzbeXdKqBfUDmElcqYl0cmmKY9KdS2UoNIz3Phapgsowi", "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 = '×'; 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:[email protected]'>")} </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="/dimden" target="_blank" style="text-decoration:none;color:#1d9bf0">@dimden</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 = '/dimden'; }); }); } 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(); } } }); tweet.addEventListener("keydown", (e) => { if(e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return; if(e.key === "y") button.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; if(window.location.pathname.includes('/settings/')) 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);