// ==UserScript==
// @name Yeah! for Twitter (Moom edit)
// @namespace https://x.com/
// @version 1.1.3
// @description Adds Yeah! button to Twitter, essentially a public Like (Moom edit)
// @author dimden.dev
// @contributor VampiricWulf
// @homepage https://github.com/vampiricwulf/YeahTwitter
// @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_moom_on32.png'] = 'data:image/png;charset=utf-8;base64,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'
YEAH_images['yeah_moom_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:admin@dimden.dev'>")}
</span>
<div class="box" style="font-family:monospace;line-break: anywhere;padding:5px;margin-top:5px;background:rgba(255, 0, 0, 0.1);color:#ff4545">
${escapeHTML(e.stack ? e.stack : String(e))} at ${t.id_str} (YeahTwitter v${chrome.runtime.getManifest().version})
</div>
</div>
`);
}
return null;
}
}
// scripts/content.js
const API_URL = `https://yeah.dimden.dev/api`;
Promise.all([
GM_fetch('https://raw.githubusercontent.com/vampiricwulf/YeahTwitter/main/styles/style.css').then(res => res.text()),
GM_fetch('https://raw.githubusercontent.com/vampiricwulf/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_moom_on32.png']}" alt=":D!" style="width: 24px; height: 24px;margin-bottom: -4px;">
Welcome to Yeah! for Twitter extension!
</h2>
<p>This extension adds a <b>:D!</b> button to all tweets, which is essentially same thing as a Like but public to everyone. Everyone can see who :D'd a tweet, and everyone can see all your :D\'s on your profile.</p>
<p>It doesn't send a spammy reply with an image, instead it saves your :D\'s 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_moom_on32.png']}" alt=":D!" style="width: 24px; height: 24px;margin-bottom: -4px;">
Authentification successful!
</h2>
<p>You can now :D! on any tweet. :D!!!!!</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">:D! (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 :D\'ing!');
}).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_moom_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_moom_on32.png'];
});
button.addEventListener('mouseout', () => {
if(!button.classList.contains('yeahed')) button.querySelector('.yeah-image').src = YEAH_images['yeah_moom_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_moom_on32.png'] : YEAH_images['yeah_moom_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_moom_on32.png'];
} else {
button.classList.remove('yeahed');
button.querySelector('.yeah-image').src = YEAH_images['yeah_moom_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 = ':D\'s';
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 = ':D\'s';
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>:D\'s</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 :D\'s 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 = ':D\'s';
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 = ':D\'s';
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 :D\'s</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 :D\'s yet';
return;
}
let tweets = await API.tweet.lookup(data);
if(!tweets.length) {
modal.querySelector('.loader').hidden = true;
list.innerHTML = 'No :D\'s 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);