- // ==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)
- }