SnapEnhance Web

A userscript to Enhance the User experience on Snapchat Web

As of 2024-01-03. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         SnapEnhance Web
// @namespace    snapenhance-web
// @description  A userscript to Enhance the User experience on Snapchat Web
// @version      1.0.2
// @author       SnapEnhance
// @source       https://github.com/SnapEnhance/web/
// @license      GPL-3.0-only
// @supportURL   https://github.com/SnapEnhance/web/issues
// @match        *://web.snapchat.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=snapchat.com
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==
(function (window) {
    function simpleHook(object, name, proxy) {
        const old = object[name];
        object[name] = proxy(old, object);
    }
    // Bypass upload size
    Object.defineProperty(File.prototype, "size", {
        get: function () {
            return 500;
        }
    });
    // Inject into worker
    function workerInjected() {
        function hookPreRequest(request) {
            if (request.url.endsWith("messagingcoreservice.MessagingCoreService/SendTypingNotification")) {
                console.log("bypassed typing notification");
                return null;
            }
            if (request.url.endsWith("messagingcoreservice.MessagingCoreService/UpdateConversation")) {
                console.log("bypassed read receipt");
                return null;
            }
            return request;
        }
        function _arrayBufferToBase64(buffer) {
            const binary = new Uint8Array(buffer).reduce((acc, byte) => acc + String.fromCharCode(byte), '');
            return btoa(binary);
        }
        async function hookPostRequest(request, response) {
            if (request.headers && request.headers.get("content-type") === "application/grpc-web+proto") {
                const arrayBuffer = await response.arrayBuffer();
                console.log("Response", response.url, _arrayBufferToBase64(arrayBuffer));
                response.arrayBuffer = async () => arrayBuffer;
            }
            return response;
        }
        // Hook websocket (hide bitmoji)
        WebSocket.prototype.send = new Proxy(WebSocket.prototype.send, {
            apply: function (target, thisArg, argumentsList) {
                console.log("WebSocket send", argumentsList[0]);
                // return target.apply(thisArg, argumentsList);
            }
        });
        // Hook worker web requests
        const oldFetch = fetch;
        // @ts-ignore 
        // eslint-disable-next-line no-implicit-globals
        fetch = async (...args) => {
            args[0] = hookPreRequest(args[0]);
            if (args[0] == null) {
                throw new Error();
            }
            const requestBody = args[0].body;
            console.log(args[0]);
            if (requestBody && !requestBody.locked) {
                const buffer = await requestBody.getReader().read();
                if (buffer.value) {
                    console.log("Request", args[0].url, _arrayBufferToBase64(buffer.value));
                }
                args[0] = new Request(args[0], {
                    body: buffer.value,
                    headers: args[0].headers
                });
            }
            // @ts-ignore
            const result = oldFetch(...args);
            return new Promise(async (resolve, reject) => {
                try {
                    resolve(await hookPostRequest(args[0], await result));
                }
                catch (e) {
                    console.info("Fetch error", e);
                    reject(e);
                }
            });
        };
    }
    const oldBlobClass = window.Blob;
    window.Blob = class HookedBlob extends Blob {
        constructor(...args) {
            const data = args[0][0];
            if (typeof data === "string" && data.startsWith("importScripts")) {
                args[0][0] += `${workerInjected.toString()};workerInjected();`;
                window.Blob = oldBlobClass;
            }
            super(...args);
        }
    };
    simpleHook(document, "createElement", (proxy, instance) => (...args) => {
        const result = proxy.call(instance, ...args);
        // Allow audio note and image download
        if (args[0] === "audio" || args[0] === "video" || args[0] === "img") {
            simpleHook(result, "setAttribute", (proxy2, instance2) => (...args2) => {
                if (args2[0] === "controlsList")
                    return;
                proxy2.call(instance2, ...args2);
            });
            result.addEventListener("contextmenu", (event) => {
                event.stopImmediatePropagation();
            });
        }
        return result;
    });
    // Always focused
    simpleHook(document, "hasFocus", () => () => true);
    const oldAddEventListener = EventTarget.prototype.addEventListener;
    Object.defineProperty(EventTarget.prototype, "addEventListener", {
        value: function (...args) {
            const eventName = args[0];
            if (eventName === "keydown")
                return;
            if (eventName === "focus")
                return;
            return oldAddEventListener.call(this, ...args);
        }
    });
})(window.unsafeWindow || window);