// ==UserScript==
// @name Instagram Profile Picture Opener
// @namespace https://github.com/GooglyBlox
// @version 1.3
// @description Adds a styled context menu option to open Instagram profile pictures in new tab
// @author GooglyBlox
// @match https://www.instagram.com/*
// @match https://www.instagram.com/
// @grant GM_addStyle
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// @run-at document-start
// @inject-into content
// @license MIT
// ==/UserScript==
(function() {
'use strict';
GM_addStyle(`
.ig-custom-context-menu {
position: fixed;
z-index: 999999;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(12px);
border: 1px solid rgba(219, 219, 219, 0.2);
border-radius: 12px;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15);
padding: 8px;
min-width: 240px;
animation: menuFadeIn 0.2s ease-out;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}
.ig-custom-context-menu-item {
display: flex;
align-items: center;
padding: 12px 16px;
color: #262626;
font-size: 14px;
font-weight: 500;
cursor: pointer;
border-radius: 8px;
transition: all 0.2s ease;
gap: 12px;
}
.ig-custom-context-menu-item:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.ig-custom-context-menu-item svg {
width: 20px;
height: 20px;
flex-shrink: 0;
}
@keyframes menuFadeIn {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.ig-custom-divider {
height: 1px;
background: rgba(219, 219, 219, 0.8);
margin: 8px 4px;
}
@media (prefers-color-scheme: dark) {
.ig-custom-context-menu {
background: rgba(38, 38, 38, 0.98);
border-color: rgba(38, 38, 38, 0.2);
}
.ig-custom-context-menu-item {
color: #fafafa;
}
.ig-custom-context-menu-item:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.ig-custom-divider {
background: rgba(38, 38, 38, 0.8);
}
}
`);
function getHDProfilePicUrl(url) {
return url;
}
const icons = {
openInNew: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>`,
download: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>`
};
function handleContextMenu(event) {
const img = event.target;
if (img.tagName === 'IMG' && (
img.closest('a[href^="/"]') !== null ||
img.closest('[role="button"]') !== null ||
img.closest('article') !== null
)) {
event.stopPropagation();
event.preventDefault();
const existingMenu = document.querySelector('.ig-custom-context-menu');
if (existingMenu) {
document.body.removeChild(existingMenu);
}
const contextMenu = document.createElement('div');
contextMenu.className = 'ig-custom-context-menu';
const rect = event.target.getBoundingClientRect();
const x = event.clientX;
const y = event.clientY;
const menuWidth = 240;
const menuHeight = 120;
const viewportWidth = window.innerWidth;
const viewportHeight = window.innerHeight;
const adjustedX = Math.min(x, viewportWidth - menuWidth - 10);
const adjustedY = Math.min(y, viewportHeight - menuHeight - 10);
contextMenu.style.left = `${adjustedX}px`;
contextMenu.style.top = `${adjustedY}px`;
const openInNewTab = document.createElement('div');
openInNewTab.className = 'ig-custom-context-menu-item';
openInNewTab.innerHTML = `${icons.openInNew}Open Profile Picture`;
openInNewTab.addEventListener('click', () => {
const hdUrl = getHDProfilePicUrl(img.src);
window.open(hdUrl, '_blank');
document.body.removeChild(contextMenu);
});
const downloadImage = document.createElement('div');
downloadImage.className = 'ig-custom-context-menu-item';
downloadImage.innerHTML = `${icons.download}Download Profile Picture`;
downloadImage.addEventListener('click', async () => {
const hdUrl = getHDProfilePicUrl(img.src);
const username = img.closest('a')?.getAttribute('href')?.replace(/\//g, '') || 'profile';
try {
const response = await fetch(hdUrl);
const blob = await response.blob();
const blobUrl = window.URL.createObjectURL(blob);
const downloadLink = document.createElement('a');
downloadLink.href = blobUrl;
downloadLink.download = `${username}_profile_picture.jpg`;
downloadLink.click();
window.URL.revokeObjectURL(blobUrl);
} catch (error) {
console.error('Failed to download image:', error);
}
document.body.removeChild(contextMenu);
});
contextMenu.appendChild(openInNewTab);
contextMenu.appendChild(document.createElement('div')).className = 'ig-custom-divider';
contextMenu.appendChild(downloadImage);
document.body.appendChild(contextMenu);
function removeContextMenu(e) {
if (!contextMenu.contains(e.target)) {
document.body.removeChild(contextMenu);
document.removeEventListener('click', removeContextMenu);
document.removeEventListener('contextmenu', removeContextMenu);
}
}
document.addEventListener('click', removeContextMenu);
document.addEventListener('contextmenu', removeContextMenu);
}
}
function init() {
document.addEventListener('contextmenu', handleContextMenu, true);
}
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.addedNodes.length) {
init();
}
});
});
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
init();
observer.observe(document.body, {
childList: true,
subtree: true
});
});
} else {
init();
observer.observe(document.body, {
childList: true,
subtree: true
});
}
})();