Google Forms Unlocker

Stops Google Forms from being locked, consequently letting you do them without a chromebook.

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name        Google Forms Unlocker
// @namespace   https://github.com/xNasuni/google-forms-unlocker
// @description Stops Google Forms from being locked, consequently letting you do them without a chromebook.
// @author      Mia @ github.com/xNasuni
// @match       *://docs.google.com/forms/*
// @grant       GM_addStyle
// @version     1.7
// @run-at		document-start
// @license     GPL-3.0
// @supportURL  https://github.com/xNasuni/google-forms-unlocker/issues
// ==/UserScript==

const kAssessmentAssistantExtensionId = "gndmhdcefbhlchkhipcnnbkcmicncehk"
const ERROR_USER_AGENT = "_useragenterror"
const ERROR_UNKNOWN = "_unknown"

var shouldSpoof = location.hash === "#gfu"

// support for browsers other than chrome.
unsafeWindow.chrome = unsafeWindow.chrome || {}
unsafeWindow.chrome.runtime = unsafeWindow.chrome.runtime || {}
unsafeWindow.chrome.runtime.sendMessage = unsafeWindow.chrome.runtime.sendMessage || function(extId, payload, callback){chrome.runtime.lastError = 1; callback()}

const oldSendMessage = unsafeWindow.chrome.runtime.sendMessage

if (GM_addStyle === undefined) {
	// https://stackoverflow.com/questions/23683439/gm-addstyle-equivalent-in-tampermonkey
	GM_addStyle = function (css) {
		const style = unsafeWindow.document.getElementById("GM_addStyleBy8626") || (function () {
			const style = unsafeWindow.document.createElement('style');
			style.type = 'text/css';
			style.id = "GM_addStyleBy8626";
			unsafeWindow.document.head.appendChild(style);
			return style;
		})();
		const sheet = style.sheet;
		sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
	}
}

function ButtonAction() {
	location.hash = "gfu"
	location.reload()
}

function MatchExtensionId(ExtensionId) {
	return ExtensionId === kAssessmentAssistantExtensionId
}

function GetGoogleForm() {
	const Containers = unsafeWindow.document.querySelectorAll("div.RGiwf")
	var Form

	for (const Container of Containers) {
		for (const Child of Container.childNodes) {
			if (Child.nodeName == "FORM") {
				Form = Child
			}
		}
	}

	return Form
}

function GetQuizHeader() {
	const QuizHeader = unsafeWindow.document.querySelector("div.mGzJpd")
	return QuizHeader
}

function PageIsErrored() {
	const QuizHeader = GetQuizHeader()
	if (QuizHeader === null) { return false }

	const ChildNodes = QuizHeader.childNodes
	if (ChildNodes[3].getAttribute("aria-live") === "assertive" && ChildNodes[4].getAttribute("aria-live") === "assertive") {
		return {title: ChildNodes[3].innerText, description: ChildNodes[4].innerText}
	}
	return false
}

function MatchErrorType(error) {
	if (error.title === "You can't access this quiz." && error.description === "Locked mode is on. Only respondents using managed Chromebooks can open this quiz. Learn more") {
		return ERROR_USER_AGENT
	}
	return ERROR_UNKNOWN
}

function MakeButton(Text, Callback, Color) {
	const Form = GetGoogleForm()
	if (Form === undefined) { return false }

	const ButtonHolder = Form.childNodes[2]

	const Button = unsafeWindow.document.createElement("div")
	Button.classList.value = "uArJ5e UQuaGc Y5sE8d TIHcue QvWxOd"
	Button.style.marginLeft = "10px"
	Button.style.backgroundColor = Color
	Button.setAttribute("role", "button")
	Button.setAttribute("tabindex", ButtonHolder.childNodes.length)
	Button.setAttribute("mia-gfu-state", "custom-button")
	ButtonHolder.appendChild(Button)

	const Glow = unsafeWindow.document.createElement("div")
	Glow.classList.value = "Fvio9d MbhUzd"
	Glow.style.top = '21px'
	Glow.style.left = '9px'
	Glow.style.width = '110px'
	Glow.style.height = '110px'
	Button.appendChild(Glow)

	const TextContainer = unsafeWindow.document.createElement("span")
	TextContainer.classList.value = "l4V7wb Fxmcue"
	Button.appendChild(TextContainer)

	const TextSpan = unsafeWindow.document.createElement("span")
	TextSpan.classList.value = "NPEfkd RveJvd snByac"
	TextSpan.innerText = Text
	TextContainer.appendChild(TextSpan)

	Button.addEventListener("click", Callback)

	return {destroy: function(){Button.remove()}}
}

async function IsOnChromebook() {
	return new Promise((resolve, _reject) => {
		oldSendMessage(kAssessmentAssistantExtensionId, {command: "isLocked"}, function(_response) {
			if (unsafeWindow.chrome.runtime.lastError) {
				resolve(false)
			}
			resolve(true)
		})
	})
}

async function Initialize() {
	GM_addStyle(`
.gfu-red {
    font-family: monospace;
    text-align: center;
    font-size: 11px;
    padding-top: 24px;
    color: red !important;
}
.EbMsme {
	transition: filter cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
	filter: blur(8px) !important;
}
.EbMsme:hover {
	filter: blur(0px) !important;
}
`)

	const Errored = PageIsErrored()
	if (Errored !== false) {
		switch (MatchErrorType(Errored)) {
			case ERROR_USER_AGENT:
				const QuizHeader = GetQuizHeader()
				const Error = unsafeWindow.document.createElement("div")
				Error.classList.value = "gfu-red"
				QuizHeader.appendChild(Error)

				const ErrorSpan = unsafeWindow.document.createElement("span")
				ErrorSpan.innerText = "Google Forms Unlocker - In order to continue, you need a User Agent Spoofer. "
				Error.appendChild(ErrorSpan)

				const AnchorSpan = unsafeWindow.document.createElement("a")
				AnchorSpan.classList.value = "gfu-red"
				AnchorSpan.innerText = "Install one here."
				AnchorSpan.target = "_blank"
				AnchorSpan.rel = "noopener"
				AnchorSpan.href = "https://github.com/xNasuni/google-forms-unlocker/blob/main/README.md#spoofing-your-user-agent"
				ErrorSpan.appendChild(AnchorSpan)
				break
			default:
				alert(`Unhandled error type: ${JSON.stringify(Errored)}`)
		}
		return
	}

	const Form = GetGoogleForm()
	if (Form === undefined) { return false }

	const IsRealManagedChromebook = await IsOnChromebook()

	if (IsRealManagedChromebook === false) {
		const ButtonHolder = Form.childNodes[2]
		for (const Button of ButtonHolder.childNodes) {
			if (Button.getAttribute("mia-gfu-state") === "custom-button") { continue }
			Button.style.backgroundColor = "#ccc"
			Button.setAttribute("jsaction", "")
		}
	}
	MakeButton("Bypass", ButtonAction, "#ff90bf")
}

var fakeIsLocked = shouldSpoof
function InterceptCommand(Payload, Callback) {
	switch (Payload.command) {
		case "isLocked":
			Callback({locked: fakeIsLocked})
			return true
		case "lock":
			if (shouldSpoof) {
				return false
			}
			fakeIsLocked = false
			Callback({locked: fakeIsLocked})
			return true
		case "unlock":
			fakeIsLocked = false
			Callback({locked: fakeIsLocked})
			return true
	}

	return false
}

setInterval(() => {
    unsafeWindow.chrome.runtime.sendMessage = function() {
        const ExtensionId = (arguments)[0]
        const Payload = (arguments)[1]
        const Callback = (arguments)[2]

        if (MatchExtensionId(ExtensionId)) {
            const Intercepted = InterceptCommand(Payload, Callback)
            if (Intercepted) { return null }
        }
		console.warn("Not intercepting", ExtensionId, Payload, Callback)

        return oldSendMessage(ExtensionId, Payload, function() {
			if (unsafeWindow.chrome.runtime.lastError) {
				alert(`Google Forms Unlocker, please report this to the GitHub https://github.com/xNasuni/google-forms-unlocker/issues\nUnhandled error: ${JSON.stringify(chrome.runtime.lastError)}`)
				return
			}
			Callback.apply(this, arguments)
		})
    }
})

unsafeWindow.document.addEventListener("DOMContentLoaded", () => {
	unsafeWindow.console.log("Initialized")
	Initialize()
})

Object.defineProperty(unsafeWindow.document, 'hidden', {
	value: false,
	writable: false
})
Object.defineProperty(unsafeWindow.document, 'visibilityState', {
	value: "visible",
	writable: false
})
Object.defineProperty(unsafeWindow.document, 'webkitVisibilityState', {
	value: "visible",
	writable: false
})
Object.defineProperty(unsafeWindow.document, 'mozVisibilityState', {
	value: "visible",
	writable: false
})
Object.defineProperty(unsafeWindow.document, 'msVisibilityState', {
	value: "visible",
	writable: false
})
const BlacklistedEvents = ['mozvisibilitychange', 'webkitvisibilitychange', 'msvisibilitychange', 'visibilitychange']
const oldAddEventListener = unsafeWindow.document.addEventListener;
unsafeWindow.document.addEventListener = function() {
	const EventType = (arguments)[0]
	const Method = (arguments)[1]
	const Options = (arguments)[2]

	if (BlacklistedEvents.indexOf(EventType) !== -1) {
		console.log(`type ${EventType} blocked from being registered with`, Method)
		return
	}

	return oldAddEventListener.apply(this, arguments)
}