Skip to content

Instantly share code, notes, and snippets.

@thibaultmol
Last active January 6, 2025 09:09
Show Gist options
  • Save thibaultmol/fdff6880f5a63fe2afc11ef2843561c7 to your computer and use it in GitHub Desktop.
Save thibaultmol/fdff6880f5a63fe2afc11ef2843561c7 to your computer and use it in GitHub Desktop.
Automatically plays animated GIFs in Kagi image search results
// ==UserScript==
// @name Kagi Image Search GIF Animator (Hover)
// @version 1
// @description Plays animated GIFs in Kagi image search results on hover
// @author Thibaultmol
// @match https://kagi.com/images*
// @updateURL https://gist.github.com/thibaultmol/fdff6880f5a63fe2afc11ef2843561c7/raw/d814a567f1d5177a496ce837160ef519c79200a4/kagi-gif-autoplay.user.js
// @downloadURL https://gist.github.com/thibaultmol/fdff6880f5a63fe2afc11ef2843561c7/raw/d814a567f1d5177a496ce837160ef519c79200a4/kagi-gif-autoplay.user.js
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
// Cache for preloaded images
const preloadCache = new Map();
// Initialize settings
if (typeof GM_getValue('preloadEnabled') === 'undefined') {
GM_setValue('preloadEnabled', true);
}
// Add menu command to toggle preloading
GM_registerMenuCommand('Toggle GIF Preloading', () => {
const currentValue = GM_getValue('preloadEnabled');
GM_setValue('preloadEnabled', !currentValue);
alert(`GIF Preloading is now ${!currentValue ? 'enabled' : 'disabled'}`);
});
function isGifUrl(url) {
return url.toLowerCase().includes('.gif');
}
function preloadImage(url) {
if (!GM_getValue('preloadEnabled')) return;
if (preloadCache.has(url)) {
return preloadCache.get(url);
}
const img = new Image();
const promise = new Promise((resolve) => {
img.onload = () => resolve(url);
img.src = url;
});
preloadCache.set(url, promise);
return promise;
}
function setupGifHoverBehavior(link) {
const img = link.querySelector('img._0_img_src');
if (!img || !link.href || !isGifUrl(link.href)) return;
const staticUrl = img.src;
const animatedUrl = link.href;
const width = img.width;
const height = img.height;
// Store dimensions and URLs as data attributes
img.dataset.staticUrl = staticUrl;
img.dataset.animatedUrl = animatedUrl;
img.dataset.width = width;
img.dataset.height = height;
// Preload if enabled
if (GM_getValue('preloadEnabled')) {
preloadImage(animatedUrl);
}
// Add hover event listeners
link.addEventListener('mouseenter', () => {
img.src = img.dataset.animatedUrl;
img.width = img.dataset.width;
img.height = img.dataset.height;
});
link.addEventListener('mouseleave', () => {
img.src = img.dataset.staticUrl;
img.width = img.dataset.width;
img.height = img.dataset.height;
});
}
function initializeGifBehavior() {
const imgLinks = document.querySelectorAll('a._0_img_link_el');
imgLinks.forEach(setupGifHoverBehavior);
}
// Initial setup
initializeGifBehavior();
// Create observer for dynamically loaded content
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
initializeGifBehavior();
}
}
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
// ==UserScript==
// @name Kagi Image Search GIF Animator
// @version 1
// @description Automatically plays animated GIFs in Kagi image search results
// @author Thibaultmol
// @match https://kagi.com/images*
// @updateURL https://gist.github.com/thibaultmol/fdff6880f5a63fe2afc11ef2843561c7/raw/f1a6e0a12294b5b9198c4c6b24ed76e36fad934e/kagi-gif-autoplay.user.js
// @downloadURL https://gist.github.com/thibaultmol/fdff6880f5a63fe2afc11ef2843561c7/raw/f1a6e0a12294b5b9198c4c6b24ed76e36fad934e/kagi-gif-autoplay.user.js
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Function to check if URL potentially points to a GIF
function isGifUrl(url) {
return url.toLowerCase().includes('.gif');
}
// Function to replace static image with animated one
function replaceWithAnimatedGif() {
const imgLinks = document.querySelectorAll('a._0_img_link_el');
imgLinks.forEach(link => {
const img = link.querySelector('img._0_img_src');
if (img && link.href && isGifUrl(link.href)) {
// Get the animated GIF URL directly from the link's href
// The href already contains the correct proxy URL structure
const animatedUrl = link.href;
// Preserve original dimensions
const width = img.width;
const height = img.height;
// Update the image source while maintaining dimensions
img.src = animatedUrl;
img.width = width;
img.height = height;
}
});
}
// Initial replacement
replaceWithAnimatedGif();
// Create observer to handle dynamically loaded content
const observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
if (mutation.addedNodes.length) {
replaceWithAnimatedGif();
}
}
});
// Start observing the document for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment