// ==UserScript== // @name Yeah! for Twitter // @namespace http://tampermonkey.net/ // @version 1.0.5 // @description Adds Yeah! button to Twitter, essentially a public Like // @author dimden.dev // @match https://x.com/* // @match https://twitter.com/* // @icon https://dimden.dev/images/yeah_logo.png // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @downloadURL none // ==/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(); } }; 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']); }); // 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;n1?n-1:0),r=1;r2&&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/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=""+e;else{const t=h(e,/^[\r\n\t ]+/);n=t&&t[0]}"application/xhtml+xml"===lt&&ot===nt&&(e=''+e+"");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="\n"+m),Me&&u([de,he,ge],(e=>{m=g(m,e," ")})),ae&&Be?ae.createHTML(m):m},o.setConfig=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};ht(e),Pe=!0},o.clearConfig=function(){mt=null,Pe=!1},o.isValidAttribute=function(e,t,n){mt||ht({});const o=ut(e),r=ut(t);return Dt(o,r,n)},o.addHook=function(e,t){"function"==typeof t&&(fe[e]=fe[e]||[],p(fe[e],t))},o.removeHook=function(e){if(fe[e])return m(fe[e])},o.removeHooks=function(e){fe[e]&&(fe[e]=[])},o.removeAllHooks=function(){fe={}},o}();return re})); //# sourceMappingURL=purify.min.js.map // scripts/api.js const publicToken = "Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA"; function getCsrf() { let csrf = document.cookie.match(/(?:^|;\s*)ct0=([0-9a-f]+)\s*(?:;|$)/); return csrf ? csrf[1] : ""; } function debugLog(...args) { if(typeof vars === "object" && vars.developerMode) { if(args[0] === 'notifications.get' && !document.querySelector('.notifications-modal') && !location.pathname.startsWith('/notifications')) return; if(vars.extensiveLogging) { console.trace(...args); } else { console.log(...args, new Error().stack.split("\n")[2].trim()); // genius } } } // extract full text and url entities from "note_tweet" function parseNoteTweet(result) { let text, entities; if(result.note_tweet.note_tweet_results.result) { text = result.note_tweet.note_tweet_results.result.text; entities = result.note_tweet.note_tweet_results.result.entity_set; if(result.note_tweet.note_tweet_results.result.richtext?.richtext_tags.length) { entities.richtext = result.note_tweet.note_tweet_results.result.richtext.richtext_tags // logically, richtext is an entity, right? } } else { text = result.note_tweet.note_tweet_results.text; entities = result.note_tweet.note_tweet_results.entity_set; } return {text, entities}; } // transform ugly useless twitter api reply to usable legacy tweet function parseTweet(res) { if(typeof res !== "object") return; if(res.limitedActionResults) { let limitation = res.limitedActionResults.limited_actions.find(l => l.action === "Reply"); if(limitation) { res.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message; } res = res.tweet; } if(!res.legacy && res.tweet) res = res.tweet; let tweet = res.legacy; if(!res.core) return; tweet.user = res.core.user_results.result.legacy; tweet.user.id_str = tweet.user_id_str; if(res.core.user_results.result.is_blue_verified && !res.core.user_results.result.legacy.verified_type) { tweet.user.verified = true; tweet.user.verified_type = "Blue"; } if(tweet.retweeted_status_result) { let result = tweet.retweeted_status_result.result; if(result.limitedActionResults && result.tweet && result.tweet.legacy) { let limitation = result.limitedActionResults.limited_actions.find(l => l.action === "Reply"); if(limitation) { result.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message; } } if(result.tweet) result = result.tweet; if( result.quoted_status_result && result.quoted_status_result.result && result.quoted_status_result.result.legacy && result.quoted_status_result.result.core && result.quoted_status_result.result.core.user_results.result.legacy ) { result.legacy.quoted_status = result.quoted_status_result.result.legacy; if(result.legacy.quoted_status) { result.legacy.quoted_status.user = result.quoted_status_result.result.core.user_results.result.legacy; result.legacy.quoted_status.user.id_str = result.legacy.quoted_status.user_id_str; if(result.quoted_status_result.result.core.user_results.result.is_blue_verified && !result.quoted_status_result.result.core.user_results.result.legacy.verified_type) { result.legacy.quoted_status.user.verified = true; result.legacy.quoted_status.user.verified_type = "Blue"; } tweetStorage[result.legacy.quoted_status.id_str] = result.legacy.quoted_status; tweetStorage[result.legacy.quoted_status.id_str].cacheDate = Date.now(); userStorage[result.legacy.quoted_status.user.id_str] = result.legacy.quoted_status.user; userStorage[result.legacy.quoted_status.user.id_str].cacheDate = Date.now(); } else { console.warn("No retweeted quoted status", result); } } else if( result.quoted_status_result && result.quoted_status_result.result && result.quoted_status_result.result.tweet && result.quoted_status_result.result.tweet.legacy && result.quoted_status_result.result.tweet.core && result.quoted_status_result.result.tweet.core.user_results.result.legacy ) { result.legacy.quoted_status = result.quoted_status_result.result.tweet.legacy; if(result.legacy.quoted_status) { result.legacy.quoted_status.user = result.quoted_status_result.result.tweet.core.user_results.result.legacy; result.legacy.quoted_status.user.id_str = result.legacy.quoted_status.user_id_str; if(result.quoted_status_result.result.tweet.core.user_results.result.is_blue_verified && !result.core.user_results.result.verified_type) { result.legacy.quoted_status.user.verified = true; result.legacy.quoted_status.user.verified_type = "Blue"; } tweetStorage[result.legacy.quoted_status.id_str] = result.legacy.quoted_status; tweetStorage[result.legacy.quoted_status.id_str].cacheDate = Date.now(); userStorage[result.legacy.quoted_status.user.id_str] = result.legacy.quoted_status.user; userStorage[result.legacy.quoted_status.user.id_str].cacheDate = Date.now(); } else { console.warn("No retweeted quoted status", result); } } tweet.retweeted_status = result.legacy; if(tweet.retweeted_status && result.core.user_results.result.legacy) { tweet.retweeted_status.user = result.core.user_results.result.legacy; tweet.retweeted_status.user.id_str = tweet.retweeted_status.user_id_str; if(result.core.user_results.result.is_blue_verified && !result.core.user_results.result.legacy.verified_type) { tweet.retweeted_status.user.verified = true; tweet.retweeted_status.user.verified_type = "Blue"; } tweet.retweeted_status.ext = {}; if(result.views) { tweet.retweeted_status.ext.views = {r: {ok: {count: +result.views.count}}}; } tweet.retweeted_status.res = res; if(res.card && res.card.legacy && res.card.legacy.binding_values) { tweet.retweeted_status.card = res.card.legacy; } tweetStorage[tweet.retweeted_status.id_str] = tweet.retweeted_status; tweetStorage[tweet.retweeted_status.id_str].cacheDate = Date.now(); userStorage[tweet.retweeted_status.user.id_str] = tweet.retweeted_status.user; userStorage[tweet.retweeted_status.user.id_str].cacheDate = Date.now(); } else { console.warn("No retweeted status", result); } if(result.note_tweet && result.note_tweet.note_tweet_results) { let note = parseNoteTweet(result); tweet.retweeted_status.full_text = note.text; tweet.retweeted_status.entities = note.entities; tweet.retweeted_status.display_text_range = undefined; // no text range for long tweets } } if(res.quoted_status_result) { tweet.quoted_status_result = res.quoted_status_result; } if(res.note_tweet && res.note_tweet.note_tweet_results) { let note = parseNoteTweet(res); tweet.full_text = note.text; tweet.entities = note.entities; tweet.display_text_range = undefined; // no text range for long tweets } if(tweet.quoted_status_result && tweet.quoted_status_result.result) { let result = tweet.quoted_status_result.result; if(!result.core && result.tweet) result = result.tweet; if(result.limitedActionResults) { let limitation = result.limitedActionResults.limited_actions.find(l => l.action === "Reply"); if(limitation) { result.tweet.legacy.limited_actions_text = limitation.prompt ? limitation.prompt.subtext.text : LOC.limited_tweet.message; } result = result.tweet; } tweet.quoted_status = result.legacy; if(tweet.quoted_status) { tweet.quoted_status.user = result.core.user_results.result.legacy; if(!tweet.quoted_status.user) { delete tweet.quoted_status; } else { tweet.quoted_status.user.id_str = tweet.quoted_status.user_id_str; if(result.core.user_results.result.is_blue_verified && !result.core.user_results.result.legacy.verified_type) { tweet.quoted_status.user.verified = true; tweet.quoted_status.user.verified_type = "Blue"; } tweet.quoted_status.ext = {}; if(result.views) { tweet.quoted_status.ext.views = {r: {ok: {count: +result.views.count}}}; } tweetStorage[tweet.quoted_status.id_str] = tweet.quoted_status; tweetStorage[tweet.quoted_status.id_str].cacheDate = Date.now(); userStorage[tweet.quoted_status.user.id_str] = tweet.quoted_status.user; userStorage[tweet.quoted_status.user.id_str].cacheDate = Date.now(); } } else { console.warn("No quoted status", result); } } if(res.card && res.card.legacy) { tweet.card = res.card.legacy; let bvo = {}; for(let i = 0; i < tweet.card.binding_values.length; i++) { let bv = tweet.card.binding_values[i]; bvo[bv.key] = bv.value; } tweet.card.binding_values = bvo; } if(res.views) { if(!tweet.ext) tweet.ext = {}; tweet.ext.views = {r: {ok: {count: +res.views.count}}}; } if(res.source) { tweet.source = res.source; } if(res.birdwatch_pivot) { // community notes tweet.birdwatch = res.birdwatch_pivot; } if(res.trusted_friends_info_result && res.trusted_friends_info_result.owner_results && res.trusted_friends_info_result.owner_results.result && res.trusted_friends_info_result.owner_results.result.legacy) { tweet.trusted_circle_owner = res.trusted_friends_info_result.owner_results.result.legacy.screen_name; } if(tweet.favorited && tweet.favorite_count === 0) { tweet.favorite_count = 1; } if(tweet.retweeted && tweet.retweet_count === 0) { tweet.retweet_count = 1; } tweet.res = res; tweetStorage[tweet.id_str] = tweet; tweetStorage[tweet.id_str].cacheDate = Date.now(); userStorage[tweet.user.id_str] = tweet.user; userStorage[tweet.user.id_str].cacheDate = Date.now(); return tweet; } const API = { account: { verifyCredentials: () => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/account/verify_credentials.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session" }, credentials: "include" }).then(response => response.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, }, user: { get: (val, byId = true) => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/users/show.json?${byId ? `user_id=${val}` : `screen_name=${val}`}`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "x-twitter-client-language": window.LANGUAGE ? window.LANGUAGE : navigator.language ? navigator.language : "en" }, credentials: "include" }).then(i => { if(i.status === 401) { setTimeout(() => { location.href = `/i/flow/login?newtwitter=true`; }, 50); } return i.json(); }).then(data => { debugLog('user.get', {val, byId, data}); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, getV2: name => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/sLVLhk0bGj3MVFEKTdax1w/UserByScreenName?variables=%7B%22screen_name%22%3A%22${name}%22%2C%22withSafetyModeUserFields%22%3Atrue%2C%22withSuperFollowsUserFields%22%3Atrue%7D&features=${encodeURIComponent(JSON.stringify({"blue_business_profile_image_shape_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true}))}`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json", "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en" }, credentials: "include" }).then(i => i.json()).then(data => { debugLog('user.getV2', 'start', {name, data}); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } if(data.data.user.result.unavailable_message) { return reject(data.data.user.result.unavailable_message.text); } let result = data.data.user.result; result.legacy.id_str = result.rest_id; if(result.legacy_extended_profile.birthdate) { result.legacy.birthdate = result.legacy_extended_profile.birthdate; } if(result.professional) { result.legacy.professional = result.professional; } if(result.affiliates_highlighted_label && result.affiliates_highlighted_label.label) { result.legacy.affiliates_highlighted_label = result.affiliates_highlighted_label.label; } if(result.is_blue_verified && !result.legacy.verified_type) { result.legacy.verified_type = "Blue"; } debugLog('user.getV2', 'end', result.legacy); resolve(result.legacy); }).catch(e => { reject(e); }); }); }, follow: screen_name => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/friendships/create.json`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, credentials: "include", body: `screen_name=${screen_name}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unfollow: screen_name => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/friendships/destroy.json`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, credentials: "include", body: `screen_name=${screen_name}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, receiveNotifications: (id, receive = false) => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/friendships/update.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&include_ext_has_nft_avatar=1&skip_status=1&cursor=-1&id=${id}&device=${receive}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, block: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/blocks/create.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unblock: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/blocks/destroy.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, mute: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/mutes/users/create.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unmute: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/mutes/users/destroy.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, removeFollower: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/QpNfg0kpPRfjROQ_9eOLXA/RemoveFollower`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json" }, credentials: "include", method: 'post', body: JSON.stringify({"variables":{"target_user_id":id},"queryId":"QpNfg0kpPRfjROQ_9eOLXA"}) }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, lookup: ids => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/users/lookup.json?user_id=${ids.join(",")}`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, credentials: "include" }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, getFollowersYouFollow: (id, cursor) => { return new Promise((resolve, reject) => { let obj = { "userId": id, "count": 50, "includePromotedContent": false }; if(cursor) obj.cursor = cursor; GM_fetch(`/i/api/graphql/m8AXvuS9H0aAI09J3ISOrw/FollowersYouKnow?variables=${encodeURIComponent(JSON.stringify(obj))}&features=${encodeURIComponent(JSON.stringify({"rweb_lists_timeline_redesign_enabled":false,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}))}`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json" }, credentials: "include" }).then(i => i.json()).then(data => { debugLog('user.getFollowersYouFollow', 'start', {id, cursor, data}); if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } let list = data.data.user.result.timeline.timeline.instructions.find(i => i.type === 'TimelineAddEntries').entries; const out = { list: list.filter(e => e.entryId.startsWith('user-')).map(e => { let user = e.content.itemContent.user_results.result; user.legacy.id_str = user.rest_id; if(user.is_blue_verified && !user.legacy.verified_type) { user.legacy.verified = true; user.legacy.verified_type = "Blue"; } return user.legacy; }), cursor: list.find(e => e.entryId.startsWith('cursor-bottom-')).content.value }; debugLog('user.getFollowersYouFollow', 'end', out); resolve(out); }).catch(e => { reject(e); }); }); }, switchRetweetsVisibility: (user_id, see) => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/friendships/update.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `id=${user_id}&retweets=${see}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, getFollowRequests: (cursor = -1) => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/friendships/incoming.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&include_can_media_tag=1&include_ext_has_nft_avatar=1&skip_status=1&cursor=${cursor}&stringify_ids=true&count=100`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session" }, credentials: "include" }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, acceptFollowRequest: user_id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/friendships/accept.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${user_id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, declineFollowRequest: user_id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/friendships/deny.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `user_id=${user_id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, }, tweet: { post: data => { // deprecated return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/statuses/update.json`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, body: new URLSearchParams(data).toString(), credentials: "include" }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, /* text | tweet_text | status - tweet text media | media_ids - media ids card_uri - card uri sensitive - sensitive media in_reply_to_status_id | in_reply_to_tweet_id - reply to tweet id exclude_reply_user_ids - exclude mentions attachment_url - quote tweet url circle - circle id conversation_control - conversation control (follows | mentions) */ postV2: tweet => { return new Promise((resolve, reject) => { let text; if(tweet.text) { text = tweet.text; } else if(tweet.tweet_text) { text = tweet.tweet_text; } else if(tweet.status) { text = tweet.status; } else { text = ""; } let variables = { "tweet_text": text, "media": { "media_entities": [], "possibly_sensitive": false }, "semantic_annotation_ids": [], "dark_request": false }; if(tweet.card_uri) { variables.card_uri = tweet.card_uri; } if(tweet.media_ids) { if(typeof tweet.media_ids === "string") { tweet.media = tweet.media_ids.split(","); } else { tweet.media = tweet.media_ids; } } if(tweet.media) { variables.media.media_entities = tweet.media.map(i => ({media_id: i, tagged_users: []})); if(tweet.sensitive) { variables.media.possibly_sensitive = true; } } if(tweet.conversation_control === 'follows') { variables.conversation_control = { mode: 'Community' }; } else if(tweet.conversation_control === 'mentions') { variables.conversation_control = { mode: 'ByInvitation' }; } if(tweet.circle) { variables.trusted_friends_control_options = { "trusted_friends_list_id": tweet.circle }; } if(tweet.in_reply_to_status_id) { tweet.in_reply_to_tweet_id = tweet.in_reply_to_status_id; delete tweet.in_reply_to_status_id; } if(tweet.in_reply_to_tweet_id) { variables.reply = { in_reply_to_tweet_id: tweet.in_reply_to_tweet_id, exclude_reply_user_ids: [] } if(tweet.exclude_reply_user_ids) { if(typeof tweet.exclude_reply_user_ids === "string") { tweet.exclude_reply_user_ids = tweet.exclude_reply_user_ids.split(","); } variables.reply.exclude_reply_user_ids = tweet.exclude_reply_user_ids; } } if(tweet.attachment_url) { variables.attachment_url = tweet.attachment_url; } debugLog('tweet.postV2', 'init', {tweet, variables}); let parsedTweet = twttr.txt.parseTweet(text); GM_fetch(`/i/api/graphql/${parsedTweet.weightedLength > 280 ? 'cuvrhmg0s4pGaLWV68NNnQ/CreateNoteTweet' : 'I_J3_LvnnihD0Gjbq5pD2g/CreateTweet'}`, { method: 'POST', headers: { "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw", "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8", "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en" }, credentials: "include", body: JSON.stringify({ variables, "features": {"c9s_tweet_anatomy_moderator_badge_enabled":true,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"responsive_web_home_pinned_timelines_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_enhance_cards_enabled":false}, "queryId": parsedTweet.weightedLength > 280 ? 'cuvrhmg0s4pGaLWV68NNnQ' : 'I_J3_LvnnihD0Gjbq5pD2g' }) }).then(i => i.json()).then(data => { debugLog('tweet.postV2', 'start', data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } let ct = data.data.create_tweet ? data.data.create_tweet : data.data.notetweet_create; let result = ct.tweet_results.result; let tweet = parseTweet(result); if(result.trusted_friends_info_result && !tweet.limited_actions) { tweet.limited_actions = 'limit_trusted_friends_tweet'; } debugLog('tweet.postV2', 'end', tweet); resolve(tweet); }).catch(e => { reject(e); }); }); }, favorite: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/lI07N6Otwv1PhnEgXILM7A/FavoriteTweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweet_id":id},"queryId":"lI07N6Otwv1PhnEgXILM7A"}) }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unfavorite: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/ZYKSe-w7KEslx3JhSIk5LA/UnfavoriteTweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweet_id":id},"queryId":"ZYKSe-w7KEslx3JhSIk5LA"}) }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, retweet: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/ojPdsZsimiJrUGLR1sjUtA/CreateRetweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweet_id":id,"dark_request":false},"queryId":"ojPdsZsimiJrUGLR1sjUtA"}) }).then(i => i.json()).then(data => { debugLog('tweet.retweet', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unretweet: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/iQtK4dl5hBmXewYZuEOKVw/DeleteRetweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"source_tweet_id":id,"dark_request":false},"queryId":"iQtK4dl5hBmXewYZuEOKVw"}) }).then(i => i.json()).then(data => { debugLog('tweet.unretweet', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, delete: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweet_id":id,"dark_request":false},"queryId":"VaenaVgh5q5ih7kvyVjgtg"}) }).then(i => i.json()).then(data => { debugLog('tweet.delete', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, get: id => { // deprecated return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/statuses/show.json?id=${id}&include_my_retweet=1&cards_platform=Web13&include_entities=1&include_user_entities=1&include_cards=1&send_error_codes=1&tweet_mode=extended&include_ext_alt_text=true&include_reply_count=true&ext=views%2CmediaStats%2CverifiedType%2CisBlueVerified`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en" }, credentials: "include" }).then(i => i.json()).then(data => { if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, vote: (api, tweet_id, card_uri, card_name, selected_choice) => { return new Promise((resolve, reject) => { GM_fetch(`https://caps.${location.hostname}/v2/capi/${api.split('//')[1]}`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `twitter%3Astring%3Acard_uri=${encodeURIComponent(card_uri)}&twitter%3Along%3Aoriginal_tweet_id=${tweet_id}&twitter%3Astring%3Aresponse_card_name=${card_name}&twitter%3Astring%3Acards_platform=Web-12&twitter%3Astring%3Aselected_choice=${selected_choice}` }).then(response => response.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }) }, createCard: card_data => { return new Promise((resolve, reject) => { GM_fetch(`https://caps.${location.hostname}/v2/cards/create.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `card_data=${encodeURIComponent(JSON.stringify(card_data))}` }).then(response => response.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }) }, mute: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/mutes/conversations/create.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `tweet_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unmute: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/mutes/conversations/destroy.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded" }, credentials: "include", method: 'post', body: `tweet_id=${id}` }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, lookup: ids => { return new Promise((resolve, reject) => { GM_fetch(`https://api.${location.hostname}/1.1/statuses/lookup.json?id=${ids.join(',')}&include_entities=true&include_ext_alt_text=true&include_card_uri=true&tweet_mode=extended&include_reply_count=true&ext=views%2CmediaStats`, { headers: { "authorization": "Bearer AAAAAAAAAAAAAAAAAAAAAFQODgEAAAAAVHTp76lzh3rFzcHbmHVvQxYYpTw%3DckAlMINMjmCwxUcaXbAN4XqJVdgMJaHqNOFgPMK0zN1qLqLQCF", "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "x-twitter-client-language": navigator.language ? navigator.language : "en" }, credentials: "include" }).then(i => i.json()).then(data => { if (data.errors && data.errors[0].code === 32) { return reject("Not logged in"); } if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, pin: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/account/pin_tweet.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, credentials: "include", method: 'post', body: `id=${id}` }).then(i => i.text()).then(data => { resolve(true); }).catch(e => { reject(e); }); }); }, unpin: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/1.1/account/unpin_tweet.json`, { headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded; charset=UTF-8" }, credentials: "include", method: 'post', body: `id=${id}` }).then(i => i.text()).then(data => { resolve(true); }).catch(e => { reject(e); }); }); }, moderate: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/pjFnHGVqCjTcZol0xcBJjw/ModerateTweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweetId":id},"queryId":"pjFnHGVqCjTcZol0xcBJjw"}) }).then(i => i.json()).then(data => { debugLog('tweet.moderate', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, unmoderate: id => { return new Promise((resolve, reject) => { GM_fetch(`/i/api/graphql/pVSyu6PA57TLvIE4nN2tsA/UnmoderateTweet`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/json; charset=utf-8" }, credentials: "include", body: JSON.stringify({"variables":{"tweetId":"1683331680751308802"},"queryId":"pVSyu6PA57TLvIE4nN2tsA"}) }).then(i => i.json()).then(data => { debugLog('tweet.unmoderate', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } resolve(data); }).catch(e => { reject(e); }); }); }, getModeratedReplies: (id, cursor) => { return new Promise((resolve, reject) => { let variables = {"rootTweetId":id,"count":20,"includePromotedContent":false}; if(cursor) variables.cursor = cursor; GM_fetch(`/i/api/graphql/SiKS1_3937rb72ytFnDHmA/ModeratedTimeline?variables=${encodeURIComponent(JSON.stringify(variables))}&features=${encodeURIComponent(JSON.stringify({"rweb_lists_timeline_redesign_enabled":false,"responsive_web_graphql_exclude_directive_enabled":true,"verified_phone_label_enabled":false,"creator_subscriptions_tweet_preview_api_enabled":true,"responsive_web_graphql_timeline_navigation_enabled":true,"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,"tweetypie_unmention_optimization_enabled":true,"responsive_web_edit_tweet_api_enabled":true,"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,"view_counts_everywhere_api_enabled":true,"longform_notetweets_consumption_enabled":true,"responsive_web_twitter_article_tweet_consumption_enabled":false,"tweet_awards_web_tipping_enabled":false,"freedom_of_speech_not_reach_fetch_enabled":true,"standardized_nudges_misinfo":true,"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,"longform_notetweets_rich_text_read_enabled":true,"longform_notetweets_inline_media_enabled":true,"responsive_web_media_download_video_enabled":false,"responsive_web_enhance_cards_enabled":false}))}`, { method: 'POST', headers: { "authorization": publicToken, "x-csrf-token": getCsrf(), "x-twitter-auth-type": "OAuth2Session", "content-type": "application/x-www-form-urlencoded", "x-twitter-client-language": LANGUAGE ? LANGUAGE : navigator.language ? navigator.language : "en" }, credentials: "include" }).then(i => i.json()).then(data => { debugLog('tweet.getModeratedReplies', 'start', id, data); if (data.errors && data.errors[0]) { return reject(data.errors[0].message); } let entries = data.data.tweet.result.timeline_response.timeline.instructions.find(i => i.entries); if(!entries) return resolve({ list: [], cursor: undefined }); entries = entries.entries; let list = entries.filter(e => e.entryId.startsWith('tweet-')); let cursor = entries.find(e => e.entryId.startsWith('cursor-bottom')); if(!cursor) { let entries = data.data.tweet.result.timeline_response.timeline.instructions.find(i => i.replaceEntry && i.replaceEntry.entryIdToReplace.includes('cursor-bottom')); if(entries) { cursor = entries.replaceEntry.entry.content.operation.cursor.value; } } else { cursor = cursor.content.operation.cursor.value; } let out = { list: list.map(e => { let tweet = parseTweet(e.content.itemContent.tweet_results.result); if(!tweet) return; tweet.moderated = true; return tweet; }).filter(e => e), cursor }; debugLog('tweet.getModeratedReplies', 'end', id, out); resolve(data); }).catch(e => { reject(e); }); }); } }, }; // scripts/helpers.js function createModal(html, className, onclose, canclose) { let modal = document.createElement('div'); modal.classList.add('yeah-modal'); let modal_content = document.createElement('div'); modal_content.classList.add('yeah-modal-content'); if(className) modal_content.classList.add(className); modal_content.innerHTML = html; modal.appendChild(modal_content); let close = document.createElement('span'); close.classList.add('yeah-modal-close'); close.title = "ESC"; close.innerHTML = '×'; 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*/`
${u.screen_name}
${escapeHTML(u.name)}
@${u.screen_name} ${u.followed_by ? `Follows you` : ''} ${label ? `
${escapeHTML(label)}` : ''}
`; 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(`

Content warnings





`); 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,iVBORw0KGgoAAAANSUhEUgAAAIAAAACACAYAAADDPmHLAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAWUSURBVHhe7Z1pqG5THMbPNV1jul1TJEOZuqYMRZEpoRARvlw+uIjwASlRFIkMHwzJ8AVfZMhYOGRKESlDkciQyJhknj3PXu9b3nP2sPba9x3Wfp5f/dpr77p1zl7Ped+11l77f5fMz8/PGV3WGByNKA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOA6AOG3eC1gGl4ammXF+h9+HZj0xAdgC3gwPhw5AHjAAL8Kz4Re8UEVTANaCT8HDijOTGy9B9t1fxVkJTWOAneAhoWky5ADIPqykKQCbQA8U84V9xz6spKlzlwyOJl9q+9B/3eI4AOI0zQIOhs+H5iJeh3fBP4qzcjaDF8DNizPTls/gDfCH4qycDeBZcLfibDEcxL8QmotJDQA7fVf4QXFWz8nwvtA0LTkJPhCatewM34LrFGej1AYg9SvgF/hNaDby8eBo2vPp4NjEl5B90hqPAcRxAMRxAMRxAMRxAMRxAMRJDcCaA2NYe3A07Ym9d236Y4TUAGwET4VlCw//Z124MjRNAmfADUOzEnb8iZB90pouS8H/QC5A1C0FMwDcUWTS4YLbz6FZCgOwFaz6Yx7LUrDJh7EsBZue0KcA/Av/Dk0TS18CwIcm/KjbEV4Nf4Qmgr4E4ErIbdAfwUvhXvB+WLkb1gS6BICzAG5Y+KTG2EfGXVn42PRDeAo8AnLjSs5wplV2b4dy3z/7IokuATgHbtfg9vBuOA04JngOHgjPhJ/D3Lgdlt3XhV4Ek0gNAL9jH4RNg66f4J2hOTX4lgx/hj3gdbBuTj1r3At/C81KuA5zD0wa96QGgB0fO+L+c3CcNt/Bi+G+8BGYw4wh9t616Y8R+jIIbMN78AR4NHyTF5RRDADhoInvPO4Pz4NfQUlUAzCE36+3wN0h34D+FUqhHoAhX8Pz4X7wSZg8rcoNB2CUt+Ex8Hj4Li/0HQdgMRxNPwY5W+D8+lvYW1IDsD6Mfc6/zeCYG3zRgq9lcf3gDsj1hEnDRZ4YNoXsk9Z02Q/wDuRKVd3CysbwQrh1cTY+WL7m2dAcG/vAa+ChcFKvzXN2ciPkGKUK7spaBfmJVYbEhpBJBICwZA7HB1dBPnnMAW8IWY3w6SJf1twb3soLueMApMFnHJfBqFJss4wDkE4vyuc4AGlwqzafLLJ4ZtY4AO0Y7sF/A57OC7nTZRYwSyViJjEL4MDvWjjJaaBLxEQyzgBsCS+Hp8FJl8p1iZgpwpU1LmLxxnJL2TTqJLtEzBTg9/yx8DV4PayttJk7DsAo3BfwOHwYruCFvuMABDhYvQm+Co+CMvdFPQB8e/lcyH0A3Bq2HpRCNQD8vY+Er0BuBZOtZKoYgF3gQ/AJuCcvKJMaAI6UaQyzUiJmOeTyLRewjoOxP/80cYmY1QDn7yy1wvk8t3hx5SwXXCImkrKVQC7XchWMu3iqdsvkwFhLxHQZA/Dfcpl02xonVR9o4d65HSCXn5+GOXc+4X6/sns7lNvtkvuxSwBmiSsgV+/4QIQFIvi0juvo3MJlauhLAPhJ9CjkfP4SmPR9qEhfAmAScQDE6RKAWSoR02dcIkYYl4gRxyVixHGJGDNeHABxHABxHABxHABxHABxUgOgUCJmFuAiTwzyJWL6ikvEmM6MbUeQ6QEOgDhNAeB/umDyprYPmwLAKpkydXN7CPuuttJpUwDehy+HpskQDuDZh5U0zQIIN1zeBg+C0yiSYNrDsrbPQL7wyh1FlcQEYAgrYjkAecAARNUwbBMA00M8DRTHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHARDHAZBmbu4/x6swK3hIFr4AAAAASUVORK5CYII='; } 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') ? '' : ''}${snippet.richtext_types.includes('Italic') ? '' : ''}`; let endingTags = `${snippet.richtext_types.includes('Bold') ? '' : ''}${snippet.richtext_types.includes('Italic') ? '' : ''}`; 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 ? `` : ''}`]; }); }; 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 => ``+ `#${escapeHTML(hashtag.text)}`+ `${hashflag ? `` : ''}`+ ``]; }); }; if (t.entities.symbols) { t.entities.symbols.forEach(symbol => { index_map[symbol.indices[0]] = [symbol.indices[1], text => ``+ `$${escapeHTML(symbol.text)}`+ ``]; }); } if (t.entities.urls) { t.entities.urls.forEach(url => { index_map[url.indices[0]] = [url.indices[1], text => ``+ `${escapeHTML(url.display_url)}`]; }); }; if (t.entities.user_mentions) { t.entities.user_mentions.forEach(user => { index_map[user.indices[0]] = [user.indices[1], text => `${escapeHTML(text)}`]; }); }; 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`
${escapeHTML(choice.label)} ${choice.selected ? `` : ''}
${isFinite(choice.percentage) ? `
${choice.count} (${choice.percentage}%)
` : '
0
'} `; 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`
${escapeHTML(choice.label)}
`; 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` `; 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`

${escapeHTML(app.title.content)}

${escapeHTML(app.category.content)}
`; 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 ? `` : ''} `; 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` `; } 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` `; } 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` `; } if(i === 1 && t.extended_entities.media.length > 3) { _html += '
'; } } if(cws.length > 0) { cws = [...new Set(cws)]; cws = "Content warnings: " + cws.join(', '); _html += html`
${cws}
`; } 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 { if(typeof seenReplies !== 'undefined' && !options.ignoreSeen) { if(seenReplies.includes(t.id_str)) return; seenReplies.push(t.id_str); } if(typeof seenThreads !== 'undefined' && !options.ignoreSeen) { if(seenThreads.includes(t.id_str)) return; } // 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 && typeof mainTweetLikers !== 'undefined' && !location.pathname.includes("retweets/with_comments") && !document.querySelector('.modal')) { tweet.addEventListener('click', async 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")) { document.getElementById('loading-box').hidden = false; savePageData(); history.pushState({}, null, `/${t.user.screen_name}/status/${t.id_str}`); updateSubpage(); mediaToUpload = []; linkColors = {}; cursor = undefined; seenReplies = []; mainTweetLikers = []; let restored = await restorePageData(); let id = location.pathname.match(/status\/(\d{1,32})/)[1]; if(subpage === 'tweet' && !restored) { updateReplies(id); } else if(subpage === 'likes') { updateLikes(id); } else if(subpage === 'retweets') { updateRetweets(id); } else if(subpage === 'retweets_with_comments') { updateRetweetsWithComments(id); } renderDiscovery(); renderTrends(); currentLocation = location.pathname; } }); } else { 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 += `@${user_mention.screen_name} ` } //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` ${t.user.name}
${mentionedUserText !== `` && !options.threadContinuation && !options.noTop && !location.pathname.includes('/status/') ? html`
${"Replying to $SCREEN_NAME$".replace('$SCREEN_NAME$', mentionedUserText.trim().replaceAll(`> <`, `>${", "}<`).replace(`>${", "}<`, `>${" and "}<`))}
`: ''}
${full_text ? await renderTweetBodyHTML(t) : ''}
${t.extended_entities && t.extended_entities.media ? html`
${t.extended_entities.media.length === 1 && t.extended_entities.media[0].type === 'video' ? html`
` : ''} ${renderMedia(t)}
${t.extended_entities && t.extended_entities.media && t.extended_entities.media.some(m => m.type === 'animated_gif') ? html`
GIF
` : ''} ` : ``} ${t.card ? `
` : ''} ${t.quoted_status ? html` ${escapeHTML(t.quoted_status.user.name)}
${escapeHTML(t.quoted_status.user.name)} @${t.quoted_status.user.screen_name}
${timeElapsed(new Date(t.quoted_status.created_at).getTime())} ${quoteMentionedUserText !== `` ? html` ${"Replying to $SCREEN_NAME$".replace('$SCREEN_NAME$', quoteMentionedUserText.trim().replaceAll(` `,", ").replace(", "," and "))} ` : ''} ${t.quoted_status.full_text ? await renderTweetBodyHTML(t, true) : ''} ${t.quoted_status.extended_entities && t.quoted_status.extended_entities.media ? html`
${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' ? '' : ''}`).join('\n')}
` : ''}
` : ``} ${t.limited_actions === 'limit_trusted_friends_tweet' && (options.mainTweet || !location.pathname.includes('/status/')) ? html`
${"This tweet is visible only to people who are in @$SCREEN_NAME$'s trusted friends circle."} ${"Learn more."}
`.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 ? `
${t.tombstone}
` : ''}
${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'}
${options.mainTweet ? '' : formatLargeNumber(t.reply_count).replace(/\s/g, ',')} ${options.mainTweet ? '' : formatLargeNumber(t.retweet_count).replace(/\s/g, ',')} ${options.mainTweet ? '' : formatLargeNumber(t.favorite_count).replace(/\s/g, ',')} ${t.ext && t.ext.views && t.ext.views.r && t.ext.views.r.ok && t.ext.views.r.ok.count ? html`${formatLargeNumber(t.ext.views.r.ok.count).replace(/\s/g, ',')}` : ''}
`; // 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, ''); text = arrayInsert(text, entity.fromIndex, ``); } text = text.join(''); div.innerHTML = html`
${escapeHTML(t.birdwatch.title)}
${text}
`; 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*/`

${"Something went wrong"}

${"Some tweets couldn't be loaded due to errors."}
${"Please copy text below and send it to $AT1$issue tracker$AT2$ or $AT3$my email$AT2$. Thank you!".replace('$AT1$', "
").replace(/\$AT2\$/g, '').replace("$AT3$", "")}
${escapeHTML(e.stack ? e.stack : String(e))} at ${t.id_str} (YeahTwitter v${chrome.runtime.getManifest().version})
`); } return null; } } // scripts/content.js const API_URL = `https://yeah.dimden.dev/api`; Promise.all([ GM_fetch('https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/styles/style.css').then(res => res.text()), GM_fetch('https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/styles/tweet.css').then(res => res.text()) ]).then(styles => { setTimeout(() => { for(let css of styles) { let style = document.createElement('style'); let head = document.head || document.getElementsByTagName('head')[0]; let isFirefox = navigator.userAgent.indexOf('Firefox') > -1; if(isFirefox) css = css.replaceAll('chrome-extension://', 'moz-extension://'); style.innerHTML = css.replaceAll('__MSG_@@extension_id__', chrome.runtime.id); head.appendChild(style); } }, 750); }); setTimeout(async () => { let yeahToken = await getYeahToken(); let ignorePopup = await new Promise(resolve => chrome.storage.local.get('ignorePopup', result => resolve(result.ignorePopup))); let userId = await getUserId(); if(!yeahToken && ignorePopup && ignorePopup[userId]) { return; } if(!yeahToken) { let modalOpenTime = Date.now(); let modal = createModal(/*html*/`

Yeah! Welcome to Yeah! for Twitter extension!

This extension adds a Yeah! button to all tweets, which is essentially same thing as a Like but public to everyone. Everyone can see who Yeahed a tweet, and everyone can see all your Yeahs on your profile.

It doesn't send a spammy reply with an image, instead it saves your Yeahs into a shared database.

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.

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.

Never show this popup for this account
`, '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*/`

Yeah! Authentification successful!

You can now Yeah! on any tweet. Yeah!!!!!

btw I (@d1mden) make a lot of cool extensions for Twitter like this, maybe u wanna follow me?
`, 'authentification-successful', () => {}, () => Date.now() - modalOpenTime > 1500); let followButton = modal2.querySelector('.follow-button'); followButton.addEventListener('click', () => { callTwitterApi('POST', '/1.1/friendships/create.json', { "Content-Type": "application/x-www-form-urlencoded" }, { include_profile_interstitial_type: 1, include_blocking: 1, include_blocked_by: 1, include_followed_by: 1, include_want_retweets: 1, include_mute_edge: 1, include_can_dm: 1, include_can_media_tag: 1, include_ext_is_blue_verified: 1, include_ext_verified_type: 1, include_ext_profile_image_shape: 1, skip_status: 1, user_id: "1708130407663759360" }).then(() => { modal2.removeModal(); alert('Thank you! Happy Yeahing!'); }).catch(e => { console.error(e); location.href = '/d1mden'; }); }); } else { throw new Error(res); } } catch(e) { console.error(e); modal.querySelector('.error-message').innerHTML = `Failed to authenticate. Please try again later. (${e.message})`; } finally { button.disabled = false; button.textContent = 'Authenticate'; if(tweetId) { callTwitterApi('POST', `/graphql/VaenaVgh5q5ih7kvyVjgtg/DeleteTweet`, {}, { variables: {tweet_id: tweetId, dark_request: false}, queryId: "VaenaVgh5q5ih7kvyVjgtg" }); } } }); let dontshow = modal.querySelector('.dontshow'); dontshow.addEventListener('click', () => { chrome.storage.local.get('ignorePopup', result => { if(!result.ignorePopup) result.ignorePopup = {}; result.ignorePopup[userId] = true; chrome.storage.local.set(result); modal.removeModal(); alert('Popup will not show again for this account. If you want to show it again, press on extension icon and press "Reset popup settings".'); }); }); }; }, 1000); let fetchQueue = []; function hookIntoTweets() { let tweets = document.getElementsByTagName('article'); for (let i = 0; i < tweets.length; i++) { let tweet = tweets[i]; if(tweet.dataset.yeahed) continue; tweet.dataset.yeahed = true; let linkToTweet = Array.from(tweet.querySelectorAll('a[role="link"]')).find(a => a.href.includes('/status/') && !a.href.includes('/photo') && !a.href.includes('/video')); let oldTwitter = false; if(!linkToTweet) { let tweetDiv = tweet.closest('.tweet, .yeah-tweet'); if(tweetDiv) { oldTwitter = true; linkToTweet = tweetDiv.querySelector('.tweet-time'); } else { continue; } }; let id = linkToTweet.href.match(/\/status\/(\d+)/)[1]; if(!id) continue; fetchQueue.push(id); let div = document.createElement('div'); let button = document.createElement('button'); button.dataset.count = tweetCache[id] ? tweetCache[id].count : 0; button.addEventListener('click', async () => { if(!await getYeahToken()) { return alert('You need to authenticate first (refresh page for auth popup to appear)'); } if(!button.classList.contains('yeahed')) { callYeahApi('/yeah', { post_id: id }); button.querySelector('.yeah-image').src = 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_on32.png'; let yeahCounter = button.querySelector('.yeah-counter'); let count = parseInt(button.dataset.count); yeahCounter.innerText = formatLargeNumber(count + 1); button.dataset.count = count + 1; button.classList.add('yeahed'); if(tweetCache[id]) { tweetCache[id].yeahed = true; tweetCache[id].count++; } let likeButton = tweet.querySelector('button[data-testid="like"], .tweet-interact-favorite:not(.tweet-interact-favorited)'); if(likeButton) { let settings = await getYeahSettings(); if(!settings.dontLike) likeButton.click(); } } else { callYeahApi('/unyeah', { post_id: id }); button.classList.remove('yeahed'); let yeahCounter = button.querySelector('.yeah-counter'); let count = parseInt(button.dataset.count); yeahCounter.innerText = formatLargeNumber(count - 1); button.dataset.count = count - 1; if(count - 1 <= 0) yeahCounter.innerText = ''; if(tweetCache[id]) { tweetCache[id].yeahed = false; tweetCache[id].count--; if(tweetCache[id].count < 0) tweetCache[id].count = 0; } let likeButton = tweet.querySelector('button[data-testid="unlike"], .tweet-interact-favorite.tweet-interact-favorited'); if(likeButton) { let settings = await getYeahSettings(); if(!settings.dontLike) likeButton.click(); } } }); button.addEventListener('mouseover', () => { button.querySelector('.yeah-image').src = 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_on32.png'; }); button.addEventListener('mouseout', () => { if(!button.classList.contains('yeahed')) button.querySelector('.yeah-image').src = 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_off32.png'; }); button.className = `yeah-button yeah-button-${id}`; div.className = 'yeah-button-container'; let img = document.createElement('img'); img.src = tweetCache[id] && tweetCache[id].yeahed ? 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_on32.png' : 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_off32.png'; if(tweetCache[id] && tweetCache[id].yeahed) button.classList.add('yeahed'); img.className = 'yeah-image'; img.draggable = false; button.appendChild(img); let counter = document.createElement('span'); counter.className = 'yeah-counter'; counter.innerText = tweetCache[id] && typeof tweetCache[id].count === 'number' ? formatLargeNumber(tweetCache[id].count) : ''; if(oldTwitter) { counter.classList.add('yeah-counter-oldtwitter'); } button.appendChild(counter); div.appendChild(button); let group = tweet.querySelector('div[role="group"]'); if(group && group.children && group.children[3]) group.children[3].after(div); else { let interactButton = tweet.querySelector('.tweet-interact-favorite, .tweet-yeah-interact-favorite'); if(interactButton) { div.classList.add('yeah-button-container-oldtwitter'); interactButton.after(div); } } } } function updateButton(data) { if(!data) return; let buttons = Array.from(document.getElementsByClassName(`yeah-button-${data.post_id}`)); for(let button of buttons) { if(data.yeahed) { button.classList.add('yeahed'); button.querySelector('.yeah-image').src = 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_on32.png'; } else { button.classList.remove('yeahed'); button.querySelector('.yeah-image').src = 'https://raw.githubusercontent.com/dimdenGD/YeahTwitter/main/images/yeah_off32.png'; } button.dataset.count = data.count; let counter = button.querySelector('.yeah-counter'); counter.innerText = data.count === 0 ? '' : formatLargeNumber(data.count); } } let tweetCache = {}; setInterval(() => tweetCache = {}, 1000 * 60 * 5); setInterval(async () => { if(fetchQueue.length > 0 && await getYeahToken()) { let first100 = fetchQueue.splice(0, 100); let cachedData = first100.map(id => tweetCache[id]).filter(Boolean); for(let cache of cachedData) { updateButton(cache); } first100 = first100.filter((id) => !tweetCache[id]); if(!first100.length) return; for(let id of first100) { tweetCache[id] = { post_id: id, yeahed: false, count: 0 }; } let data = JSON.parse(await callYeahApi('/get', { post_ids: first100.join(',') })); for(let i in data) { tweetCache[data[i].post_id] = data[i]; updateButton(data[i]); } } }, 1500); function hookIntoInteractions() { let path = window.location.pathname; let addedTab; if(path.includes('/status/') && (path.endsWith('/quotes') || path.endsWith('/retweets') || path.endsWith('/likes'))) { let tablist = document.querySelector('div[role="tablist"]'); if(!tablist) return; if(tablist.dataset.yeahed) return; tablist.dataset.yeahed = true; let yeahTab = document.createElement('div'); yeahTab.className = 'yeah-tab'; let span = document.createElement('span'); span.innerText = 'Yeahs'; yeahTab.appendChild(span); tablist.appendChild(yeahTab); addedTab = yeahTab; } else { let tablist = document.querySelector('.tweet-footer-stats'); if(!tablist) return; if(tablist.dataset.yeahed) return; tablist.dataset.yeahed = true; let yeahTab = document.createElement('a'); yeahTab.className = 'tweet-footer-stat'; yeahTab.style.cursor = 'pointer'; let span = document.createElement('span'); span.innerText = 'Yeahs'; span.className = 'tweet-footer-stat-text'; let b = document.createElement('b'); let id = location.pathname.match(/\/status\/(\d+)/)[1]; b.innerText = tweetCache[id] && typeof tweetCache[id].count === 'number' ? formatLargeNumber(tweetCache[id].count) : '?'; b.className = 'tweet-footer-stat-count'; yeahTab.appendChild(span); yeahTab.appendChild(b); tablist.appendChild(yeahTab); addedTab = yeahTab; } if(addedTab) { addedTab.addEventListener('click', async() => { if(!await getYeahToken()) { return alert('You need to authenticate first (refresh page for auth popup to appear)'); } let modal = createModal(/*html*/`

Yeahs

`, 'yeah-users'); let list = modal.querySelector('.list'); let data = JSON.parse(await callYeahApi('/get_users', { post_id: path.match(/\/status\/(\d+)/)[1], page: 1 })); if(!data.length) { modal.querySelector('.loader').hidden = true; list.innerHTML = 'No Yeahs yet'; return; } let lookup = await API.user.lookup(data); modal.querySelector('.loader').hidden = true; let addedUsers = []; for(let id of data) { let user = lookup.find(user => user.id_str === id); if(user) { appendUser(user, list); addedUsers.push(user.id_str); } } let modalContent = modal.querySelector('.yeah-modal-content'); let over = false, loadingMore = false, page = 2; modalContent.addEventListener('scroll', async () => { if(over) return; if(loadingMore) return; let scrollPosition = modalContent.scrollTop + modalContent.offsetHeight; if(scrollPosition >= modalContent.scrollHeight - 200) { loadingMore = true; modal.querySelector('.loader').hidden = false; let data = JSON.parse(await callYeahApi('/get_users', { post_id: path.match(/\/status\/(\d+)/)[1], page: page++ })); if(!data.length) { over = true; modal.querySelector('.loader').hidden = true; return; } let lookup = await API.user.lookup(data); for(let id of data) { if(addedUsers.includes(id)) continue; let user = lookup.find(user => user.id_str === id); if(user) { appendUser(user, list); addedUsers.push(user.id_str); } } loadingMore = false; modal.querySelector('.loader').hidden = true; } }); }); } } function hookIntoProfile() { if(['/notifications', '/explore', '/home', '/messages'].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('/messages/')) return; if(window.location.pathname.includes('/communities/')) return; let addedTab; let profileStats = document.querySelector('#profile-stats'); if(!profileStats) { let tablist = document.querySelector('div:not([data-testid="toolBar"]) > nav[role="navigation"][aria-live="polite"] div div[role="tablist"]'); if(!tablist) return; if(tablist.dataset.yeahed) return; tablist.dataset.yeahed = true; let yeahTab = document.createElement('div'); yeahTab.className = 'yeah-tab'; let span = document.createElement('span'); span.innerText = 'Yeahs'; yeahTab.appendChild(span); tablist.appendChild(yeahTab); addedTab = yeahTab; } else { if(profileStats.dataset.yeahed) return; profileStats.dataset.yeahed = true; let yeahTab = document.createElement('a'); yeahTab.className = 'profile-stat'; yeahTab.style.cursor = 'pointer'; let span = document.createElement('span'); span.innerText = 'Yeahs'; span.className = 'profile-stat-text'; let span2 = document.createElement('span'); span2.className = 'profile-stat-value'; span2.innerText = '?'; setTimeout(() => { let avatar = document.getElementById('profile-avatar'); if(!avatar || !avatar.dataset.user_id) return; let id = avatar.dataset.user_id; callYeahApi('/get_user_yeah_count', { user_id: id }).then(data => { data = JSON.parse(data); if(typeof data.count === 'number') span2.innerText = formatLargeNumber(data.count); }); }, 2000); yeahTab.appendChild(span); yeahTab.appendChild(span2); profileStats.appendChild(yeahTab); addedTab = yeahTab; } if(addedTab) addedTab.addEventListener('click', async () => { if(!await getYeahToken()) { return alert('You need to authenticate first (refresh page for auth popup to appear)'); } let username = window.location.pathname.split('/')[1]; let modal = createModal(/*html*/`

${username}'s Yeahs

`, 'yeah-posts'); let list = modal.querySelector('.list'); let user = await API.user.get(username, false); let data = JSON.parse(await callYeahApi('/get_yeahs', { user_id: user.id_str, page: 1 })); if(!data.length) { modal.querySelector('.loader').hidden = true; list.innerHTML = 'No Yeahs yet'; return; } let tweets = await API.tweet.lookup(data); if(!tweets.length) { modal.querySelector('.loader').hidden = true; list.innerHTML = 'No Yeahs yet'; return; } let addedPosts = []; for(let id of data) { let tweet = tweets.find(tweet => tweet.id_str === id); if(tweet) { 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) { 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);