Skip to content

Instantly share code, notes, and snippets.

@busybox11
Last active August 29, 2023 04:52
Show Gist options
  • Star 12 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save busybox11/270a9ca89a26c79f467f214761303242 to your computer and use it in GitHub Desktop.
Save busybox11/270a9ca89a26c79f467f214761303242 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name @chaoticvibing Twitter Blue Clown - twitter.com
// @namespace Violentmonkey Scripts
// @match *://*.twitter.com/*
// @grant none
// @version 1.9.0clown
// @author @chaoticvibing - GH @busybox11
// @description 11/9/2022, 11:45:28 PM
// @updateURL https://gist.githubusercontent.com/busybox11/270a9ca89a26c79f467f214761303242/raw/twitterblue-nerd.js
// @downloadURL https://gist.githubusercontent.com/busybox11/270a9ca89a26c79f467f214761303242/raw/twitterblue-nerd.js
// ==/UserScript==
// YOU'RE FREE TO DO WHATEVER YOU WANT WITH THIS SCRIPT BUT IF YOU DO MAKE SOMETHING
// PLEASE MAKE SURE TO MENTION ME SOMEWHERE - I hope you'll understand why :)
// Also https://paypal.me/busybox11 because I am broke
/* INSTRUCTIONS
*
* - Install a userscript browser extension
* (I used ViolentMonkey https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag,
* but you can use any extension you want, such as tampermonkey, it should work fine)
* FIREFOX USERS: It seems to work better with TamperMonkey: https://addons.mozilla.org/fr/firefox/addon/tampermonkey/
* - Import the script
* On ViolentMonkey, click on the extension icon, then gear icon (Open dashboard)
* There should be a plus icon on the top left hand corner, click on it and select Install from URL
* Use this URL: https://gist.githubusercontent.com/busybox11/270a9ca89a26c79f467f214761303242/raw/twitterblue-nerd.js
* It should now work and update by itself
*
*/
// YOU'RE FREE TO DO WHATEVER YOU WANT WITH THIS SCRIPT BUT IF YOU DO MAKE SOMETHING
// PLEASE MAKE SURE TO MENTION ME SOMEWHERE - I hope you'll understand why :)
// Also https://paypal.me/busybox11 because I am broke
// Commissions on https://uncove.com/busybox11
// 1.1.0 ALSO UPDATE ON HEADER OF PROFILE
// 1.1.1 AUTO UPDATE
// 1.1.2 Better handling of verified notifications
// 1.1.3 Better error logging
// 1.2.0 INITIAL VERY WIP FIREFOX SUPPORT
// 1.2.1 Misc code quality changes
// 1.3.0 MADE IT WORK WITH THEORETICALLY ALL NOTIFICATIONS
// 1.4.0 ALSO SHOW ON PROFILE VERIFICATION POPUP
// 1.5.0 WIP SHOULD WORK ON ALL LANGUAGES
// 1.6.0 PATH-BASED BLUE CHECK DETECTION
// 1.7.0 SOMEHOW FIXES NEWER FIREFOX VERSIONS
// 1.7.1 FIREFOX AGAIN...
// 1.8.0 TWITTER CHANGED STUFF AGAIN PLEASE SEND HELP
// 1.9.0 UPDATED POPUP BECAUSE TWITTER (elon) HAVE DECIDED TO BE DUMB (thanks @tom-wharf for the update!)
/*
* INSTRUCTIONS
*
* - Install a userscript browser extension
* (I used ViolentMonkey https://chrome.google.com/webstore/detail/violentmonkey/jinjaccalgkegednnccohejagnlnfdag,
* but you can use any extension you want, such as tampermonkey, it should work fine)
* FIREFOX USERS: It seems to work better with TamperMonkey: https://addons.mozilla.org/fr/firefox/addon/tampermonkey/
* - Import the script
* On ViolentMonkey, click on the extension icon, then gear icon (Open dashboard)
* There should be a plus icon on the top left hand corner, click on it and select Install from URL
* Use this URL: https://gist.githubusercontent.com/busybox11/53c76f57a577a47a19fab649a76f18e3/raw/twitterblue-nerd.js
* It should now work and update by itself
*
*/
/*
* DISCLAIMER
*
* I made this in a rush because of a challenge I convinced myself to do in reply to a tweet:
* https://twitter.com/Quinten0508/status/1590464705822224384?s=20&t=R_KhoR4a-_3fI4n4mbmmGA
* It might have horrible performance and it could not be reliable as I've tested this very quickly
* on some places I could find Twitter blue checkmarks, but I haven't made much research on it.
* At least it runs fine on my Ryzen 9 5900HS laptop and I don't see any noticeable frame drops
* on my 165Hz QHD display since I made this script, which might be a sign it's not impacting much.
* (I don't care anyway, fell free to modify it if it isn't)
*
*/
// STOLEN FROM https://twitter.com/shadowbIood/status/1590462560515473409?s=20&t=AmfQmmFgdpKOsPqnoawjVQ
const nerdtick = `
<g>
<path d="m37.724 16.691c1.073-3.147 0.907-6.541-0.467-9.558s-3.792-5.293-6.807-6.414c-4.546-1.687-9.569-0.321-12.769 3.242-2.339-0.935-4.893-0.993-7.297-0.15-2.737 0.961-4.952 2.977-6.237 5.676-1.141 2.398-1.421 5.1-0.828 7.655-1.161 1.266-2.055 2.776-2.625 4.449-2.14 6.28 0.837 13.183 6.659 15.724 0.202 0.088 0.407 0.171 0.615 0.248 3.812 1.414 7.999 0.741 11.23-1.8 1.744-1.172 6.853-5.936 9.194-8.195 8.385-8.097 9.074-10.117 9.332-10.877zm29.885 10.954c2.341 2.26 7.45 7.024 9.194 8.195 3.231 2.541 7.418 3.215 11.23 1.8 0.208-0.078 0.413-0.16 0.615-0.248 5.823-2.541 8.799-9.444 6.659-15.724-0.569-1.673-1.464-3.183-2.625-4.449 0.592-2.556 0.313-5.257-0.828-7.655-1.285-2.7-3.499-4.715-6.237-5.677-2.404-0.844-4.958-0.785-7.297 0.15-3.2-3.563-8.223-4.929-12.769-3.242-3.015 1.121-5.433 3.397-6.807 6.414s-1.54 6.411-0.467 9.558c0.258 0.76 0.946 2.78 9.332 10.878z" fill="#0092FF"/>
<path d="m92.224 52.048c0-6.944-3.685-12.966-9.046-15.823 0.649-1.912 1.002-3.978 1.002-6.153 0-9.713-7.202-17.572-16.08-17.572-1.979 0-3.875 0.369-5.627 1.099-2.603-5.876-8.111-9.898-14.471-9.898s-11.86 4.03-14.475 9.889c-1.748-0.725-3.647-1.099-5.627-1.099-8.886 0-16.08 7.867-16.08 17.581 0 2.171 0.35 4.237 0.998 6.153-5.357 2.857-9.042 8.87-9.042 15.823 0 6.571 3.293 12.298 8.179 15.322-0.084 0.747-0.135 1.494-0.135 2.259 0 9.713 7.193 17.581 16.08 17.581 1.979 0 3.875-0.378 5.622-1.099 2.612 5.863 8.113 9.889 14.476 9.889 6.368 0 11.868-4.026 14.475-9.889 1.748 0.716 3.643 1.09 5.627 1.09 8.886 0 16.08-7.867 16.08-17.581 0-0.765-0.051-1.512-0.139-2.255 4.877-3.02 8.183-8.746 8.183-15.313v-4e-3z" fill="#FEE7B8"/>
<ellipse cx="23.209" cy="52.616" rx="7.894" ry="6.077" fill="#F97E58"/>
<ellipse cx="76.004" cy="52.616" rx="7.894" ry="6.077" fill="#F97E58"/>
<path d="m49.31 83.149c-7.662 0-14.99-3.42-20.104-9.382-2.282-2.66-2.065-6.748 0.484-9.129 2.549-2.382 6.466-2.155 8.748 0.505 2.767 3.226 6.729 5.076 10.872 5.076 4.142 0 8.105-1.85 10.872-5.076 2.282-2.661 6.199-2.887 8.748-0.505 2.55 2.381 2.766 6.469 0.484 9.129-5.115 5.962-12.442 9.382-20.104 9.382z" fill="#c33"/>
<path d="m49.31 77.429c-6.106 0-11.945-2.726-16.021-7.478-0.263-0.307-0.238-0.779 0.056-1.053 0.294-0.275 0.746-0.249 1.009 0.058 3.805 4.436 9.256 6.98 14.956 6.98 5.699 0 11.15-2.544 14.955-6.98 0.263-0.307 0.715-0.334 1.01-0.058 0.294 0.274 0.319 0.746 0.056 1.053-4.076 4.753-9.916 7.478-16.021 7.478z" fill="#FF6700"/>
<path d="m59.623 50.864c0 5.773-4.484 10.452-10.016 10.452s-10.016-4.679-10.016-10.452 4.484-10.453 10.016-10.453 10.016 4.68 10.016 10.453z" fill="#c33"/>
<ellipse cx="31.103" cy="28.795" rx="5.889" ry="9.573" fill="#282D35"/>
<ellipse cx="65.513" cy="28.795" rx="5.889" ry="9.573" fill="#282D35"/>
</g>
`
let regularVerifiedPath = 'svg path[d^="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z"]'
let otherRegularVerifiedPath = 'svg path[d^="M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.607.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.882-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.902-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.144 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.567s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.907.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072 4.4-4.794 1.347 1.246z"]'
const regulartick = `
<g><path d="M22.25 12c0-1.43-.88-2.67-2.19-3.34.46-1.39.2-2.9-.81-3.91s-2.52-1.27-3.91-.81c-.66-1.31-1.91-2.19-3.34-2.19s-2.67.88-3.33 2.19c-1.4-.46-2.91-.2-3.92.81s-1.26 2.52-.8 3.91c-1.31.67-2.2 1.91-2.2 3.34s.89 2.67 2.2 3.34c-.46 1.39-.21 2.9.8 3.91s2.52 1.26 3.91.81c.67 1.31 1.91 2.19 3.34 2.19s2.68-.88 3.34-2.19c1.39.45 2.9.2 3.91-.81s1.27-2.52.81-3.91c1.31-.67 2.19-1.91 2.19-3.34zm-11.71 4.2L6.8 12.46l1.41-1.42 2.26 2.26 4.8-5.23 1.47 1.36-6.2 6.77z"></path></g>
`
// EDITED, STOLEN FROM https://stackoverflow.com/questions/70507318/how-to-get-react-element-props-from-html-element-with-javascript
function getReactProps(parent, target) {
parent = parent.wrappedJSObject ?? parent;
target = target.wrappedJSObject ?? target;
const keyof_ReactProps = Object.keys(parent).find(k => k.startsWith("__reactProps$"));
const symof_ReactFragment = Symbol.for("react.fragment");
// Find the path from target to parent
let path = [];
let elem = target;
while (elem !== parent) {
let index = 0;
for (let sibling = elem; sibling != null;) {
if (sibling[keyof_ReactProps]) index++;
sibling = sibling.previousElementSibling;
}
path.push({ child: elem, index });
elem = elem.parentElement;
}
// Walk down the path to find the react state props
let state = elem[keyof_ReactProps];
for (let i = path.length - 1; i >= 0 && state != null; i--) {
// Find the target child state index
let childStateIndex = 0, childElemIndex = 0;
while (childStateIndex < state.children.length) {
let childState = state.children[childStateIndex];
if (childState instanceof Object) {
// Fragment children are inlined in the parent DOM element
let isFragment = childState.type === symof_ReactFragment && childState.props.children.length;
childElemIndex += isFragment ? childState.props.children.length : 1;
if (childElemIndex === path[i].index) break;
}
childStateIndex++;
}
let childState = state.children[childStateIndex] ?? (childStateIndex === 0 ? state.children : null);
state = childState?.props;
elem = path[i].child;
}
return state;
}
function updateBlueTick(elem, props) {
if (props.isBlueVerified) {
elem.setAttribute('viewBox', '0 0 96 96')
elem.innerHTML = nerdtick
} else {
elem.setAttribute('viewBox', '0 0 24 24')
elem.innerHTML = regulartick
}
}
function bluetickHandling(bluetick) {
let propsElem = getReactProps(bluetick.parentElement.parentElement, bluetick.parentElement)
if (propsElem.children !== undefined) {
let props;
try {
props = propsElem.children[0][0].props
} catch(e) {
propsElem = getReactProps(bluetick.parentElement.parentElement.parentElement.parentElement, bluetick.parentElement.parentElement.parentElement)
props = propsElem.children[0][0].props
sc.log(props)
}
if (props.isBlueVerified !== undefined) {
updateBlueTick(bluetick, props)
} else {
const otherProps = propsElem.children[0][propsElem.children[0].length - 1].props
updateBlueTick(bluetick, otherProps)
}
} else {
const propsElemParent = getReactProps(bluetick.parentElement.parentElement.parentElement, bluetick.parentElement.parentElement)
const propsParent = propsElemParent.children[0][0].props
updateBlueTick(bluetick, propsParent)
}
}
function handleMutation(mutations) {
try {
for (let mutation of mutations) {
for (let elem of mutation.addedNodes) {
// SVG PATH DETECTION
// Thanks GH @artesea - https://gist.github.com/busybox11/53c76f57a577a47a19fab649a76f18e3?permalink_comment_id=4366043#gistcomment-4366043
// Author of this snippet
let blueticksPath = elem.querySelectorAll(regularVerifiedPath)
let differentBlueticksPath = elem.querySelectorAll(otherRegularVerifiedPath)
const blueTicks = [
...blueticksPath,
...differentBlueticksPath
]
for (let bluetick of blueTicks.flat()) {
try {
if (bluetick !== null) {
bluetickHandling(bluetick.parentElement.parentElement)
}
} catch(e) {sc.log("blueTicks check", bluetick, e)}
}
// PROFILE POPUPS
const profileBlueticks = elem.querySelectorAll('.css-1dbjc4n.r-xoduu5.r-1pcd2l5')
try {
for (let profileBluetick of profileBlueticks) {
if (profileBluetick !== null) {
let tickChild = profileBluetick.firstChild
let tickChildProps = getReactProps(profileBluetick.parentElement, profileBluetick)
updateBlueTick(tickChild, tickChildProps)
}
}
} catch(e) {sc.log(e)}
}
}
} catch(e) {sc.log(e)}
}
const sc = {
log: (...args) => {
console.log('[nerdtick]', ...args)
}
}
const observer = new MutationObserver(handleMutation)
observer.observe(document, { childList: true, subtree: true })
@PrissyPineapple
Copy link

I used it on Android on Kiwi Browser, using Tampermonkey. Works perfectly.

I used it on my desktop too, on Firefox with Tampermoneky, works great there too. The icon is a bit difficult to see on dark mode, but it's otherwise perfect. Thank you so much.

@busybox11
Copy link
Author

Ah, I never tested it on a phone. Thanks for the feedback, that's good to know!
Unfortunately, since this is basically a copy-paste from the clown emoji, I can't do much about the visibility in dark mode. It looks OK with my computer but I guess that's using dim mode instead of lights out.
You're very welcome, I'm glad to help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment