Last active
March 31, 2025 22:17
-
-
Save varenc/20e4dbfe8e7a2cc305043ffcbc5454d0 to your computer and use it in GitHub Desktop.
Unbreak Snapchat web. Disable focus tracking and screenshot prevention
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Unbreak Snapchat web. Disable focus tracking, screenshot prevention, and enable video downloads | |
// @name:zh-CN 修复Snapchat网络版:禁用焦点追踪、屏幕截图防护,并启用视频下载 | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1.4 | |
// @description Improve the Snapchat web experience by disabling screenshot prevention features which don't prevent screenshots but do actively harm the usability. And enabling video downloads | |
// @description:zh-CN 通过禁用防护功能并启用视频下载,改善Snapchat网络版的体验。 | |
// @homepage https://gist.github.com/varenc/20e4dbfe8e7a2cc305043ffcbc5454d0 | |
// @author https://github.com/varenc | |
// @match https://web.snapchat.com/* | |
// @match https://www.snapchat.com/web/* | |
// @icon http://snapchat.com/favicon.ico | |
// @license MIT | |
// @run-at document-idle | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
function __unblockControlKeyEvents() { | |
const events = ["keydown", "keyup", "keypress"]; | |
const modifyKeys = ["Control", "Meta", "Alt", "Shift"]; | |
for (var i = 0; i < events.length; i++) { | |
var event_type = events[i]; | |
document.addEventListener( | |
event_type, | |
function (e) { | |
// console.log(`${event_type}[${i}]=`, e.key); | |
if (modifyKeys.includes(e.key)) { | |
e.preventDefault(); | |
e.stopPropagation(); | |
console.log(`'${event_type}' event for '${e.key}' received and prevented:`, e); | |
e.stopImmediatePropagation(); | |
} | |
}, | |
true | |
); | |
} | |
} | |
function __unblockEvent() { | |
for (var i = 0; i < arguments.length; i++) { | |
var event_type = arguments[i]; | |
document.addEventListener( | |
arguments[i], | |
function (e) { | |
// e.preventDefault(); | |
e.stopPropagation(); | |
console.log(`'${event_type}' event received and prevented:`, e); | |
// e.stopImmediatePropagation(); | |
}, | |
true | |
); | |
} | |
} | |
function __fixConsole() { | |
// snapchat tries to disable console.log.. how mean. So we copy the real Console object from a new iframe | |
const iframe = document.createElement("iframe"); | |
iframe.style.display = "none"; | |
document.body.appendChild(iframe); | |
const nativeConsole = iframe.contentWindow.console; | |
window.console = nativeConsole; | |
} | |
function __enableVideoDownloads() { | |
// Process all videos on the page, making them downloadable and fully functional | |
document.querySelectorAll("video").forEach((video, index) => { | |
console.log(`VideoFixer: Processing <video> #${index + 1}:`, video); | |
// Make video visible and interactive | |
video.style.position = "relative"; | |
video.style.zIndex = "999999"; | |
video.controls = true; | |
video.style.pointerEvents = "auto"; | |
// Track and remove restrictive attributes | |
const removedAttributes = []; | |
const removedControlsListItems = []; | |
// Remove attributes that restrict functionality | |
["disablePictureInPicture", "disableRemotePlayback"].forEach((attr) => { | |
if (video.hasAttribute(attr)) { | |
removedAttributes.push(attr); | |
video.removeAttribute(attr); | |
} | |
}); | |
// Remove controlsList restrictions | |
if (video.hasAttribute("controlsList")) { | |
removedControlsListItems.push(...video.getAttribute("controlsList").split(/\s+/)); | |
video.removeAttribute("controlsList"); | |
} | |
}); | |
console.log("VideoFixer: All videos processed and fixed!"); | |
} | |
// Set up a MutationObserver to detect new videos being added to the page | |
function __setupVideoObserver() { | |
// Create a MutationObserver to watch for new video elements | |
const videoObserver = new MutationObserver((mutations) => { | |
let shouldProcess = false; | |
// Check if any videos were added or modified | |
for (const mutation of mutations) { | |
if (mutation.type === 'childList') { | |
const addedVideos = Array.from(mutation.addedNodes).filter(node => | |
node.nodeName === 'VIDEO' || | |
(node.nodeType === Node.ELEMENT_NODE && node.querySelector('video')) | |
); | |
if (addedVideos.length > 0) { | |
shouldProcess = true; | |
break; | |
} | |
} else if (mutation.type === 'attributes' && | |
mutation.target.nodeName === 'VIDEO') { | |
shouldProcess = true; | |
break; | |
} | |
} | |
// If videos were added or modified, process all videos | |
if (shouldProcess) { | |
__enableVideoDownloads(); | |
} | |
}); | |
videoObserver.observe(document.body, { | |
childList: true, | |
subtree: true, | |
attributes: true, | |
attributeFilter: ['src', 'controlsList', 'disablePictureInPicture', 'disableRemotePlayback'], | |
attributeOldValue: true | |
}); | |
return videoObserver; | |
} | |
function __setupUnblocker() { | |
__fixConsole(); | |
__unblockControlKeyEvents(); | |
///////// NOTE ///////// | |
// The below makes right-click work like normal, but it also disables Snapchat's special right click behavior in certain places. | |
__unblockEvent("contextmenu"); | |
// Process any videos that are already on the page | |
__enableVideoDownloads(); | |
} | |
console.dir("Snapchat unbreaker running!") // Snapchat doesn't disable `console.dir`... silly Snapchat. | |
__setupUnblocker(); | |
// Run a few extra times to ensure our event listeners take priority. | |
setTimeout(__setupUnblocker, 1000); | |
setTimeout(__setupUnblocker, 5000); | |
setTimeout(__setupUnblocker, 10000); | |
// Also set up a periodic check every 30 seconds as a fallback | |
setInterval(__enableVideoDownloads, 30000); | |
// Works 90% of the time, but they also use some other method to check focus I haven't tracked down yet. | |
document.hasFocus = function(){return true} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment