Skip to content

Instantly share code, notes, and snippets.

@scarf005
Created April 6, 2025 03:17
Show Gist options
  • Save scarf005/90faf4e2bbe0e61bc5c243e202921070 to your computer and use it in GitHub Desktop.
Save scarf005/90faf4e2bbe0e61bc5c243e202921070 to your computer and use it in GitHub Desktop.
Replace thumbs-down with baps
// ==UserScript==
// @name GitHub Custom Thumbs Down
// @namespace https://github.com/scarf005
// @version 1.0.0
// @description Replaces the default GitHub thumbs down emoji with a custom image
// @author scarf
// @match https://github.com/*
// @grant none
// @run-at document-idle
// ==/UserScript==
(() => {
'use strict'
/**
* The URL of the custom emoji image
* Using size=20 to match GitHub's typical reaction emoji size
* @type {string}
*/
const CUSTOM_EMOJI_URL = 'https://cdn.discordapp.com/emojis/885793151263068160.webp?size=20'
/**
* The CSS selector to find the GitHub thumbs down emoji elements
* Targets the <g-emoji> wrapper with the specific alias
* @type {string}
*/
const THUMBS_DOWN_SELECTOR = 'g-emoji[alias="-1"]'
/**
* Updates a given emoji element (g-emoji) to use the custom image
* @param {Element} gEmojiElement - The <g-emoji> element to modify
* @returns {void}
*/
const replaceEmojiElement = (gEmojiElement) => {
/** @type {HTMLImageElement | null} */
const img = gEmojiElement.querySelector('img')
// Check if the image exists and hasn't already been replaced
if (img && img.src !== CUSTOM_EMOJI_URL) {
img.src = CUSTOM_EMOJI_URL
img.srcset = '' // Clear srcset to ensure our src is used
img.width = 20 // Ensure consistent size
img.height = 20 // Ensure consistent size
// Optional: Update fallback src on parent, though less critical
// gEmojiElement.setAttribute('fallback-src', CUSTOM_EMOJI_URL)
// console.log('Replaced thumbs down emoji:', img) // For debugging
}
}
/**
* Finds and replaces all thumbs down emojis within a given node or the entire document
* @param {Node} parentNode - The node to search within (defaults to document)
* @returns {void}
*/
const replaceAllThumbsDown = (parentNode = document) => {
/** @type {NodeListOf<Element>} */
const emojiElements = parentNode.querySelectorAll(THUMBS_DOWN_SELECTOR)
emojiElements.forEach(replaceEmojiElement)
}
/**
* Callback function for the MutationObserver
* Checks added nodes for relevant emoji elements
* @param {MutationRecord[]} mutationsList
* @param {MutationObserver} observer
* @returns {void}
*/
const handleMutations = (mutationsList, observer) => {
mutationsList.forEach((mutation) => {
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
// Check if the added node is an element node
if (node.nodeType === Node.ELEMENT_NODE) {
/** @type {Element} */
const element = node
// Check if the node itself is the target emoji
if (element.matches && element.matches(THUMBS_DOWN_SELECTOR)) {
replaceEmojiElement(element)
}
// Check if the node contains any target emojis (important for larger chunks of HTML)
// Use querySelectorAll on the element itself for efficiency
replaceAllThumbsDown(element)
}
})
}
})
}
// --- Main Execution ---
// 1. Initial replacement run for emojis present on page load
replaceAllThumbsDown(document.body)
// 2. Set up MutationObserver to watch for dynamically added content
/** @type {MutationObserverInit} */
const observerConfig = {
childList: true, // Watch for addition/removal of children
subtree: true // Watch descendants as well
}
/** @type {MutationObserver} */
const observer = new MutationObserver(handleMutations)
// 3. Start observing the document body
// Use document.body as it's generally available and containers change often on GitHub
if (document.body) {
observer.observe(document.body, observerConfig)
} else {
// Fallback if body isn't immediately ready (less likely with @run-at document-idle)
document.addEventListener('DOMContentLoaded', () => observer.observe(document.body, observerConfig))
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment