- // ==UserScript==
- // @name Google Formify
- // @version 2.6
- // @description Aid Google Form with Gemini AI
- // @author rohitaryal
- // @license MIT
- // @grant GM_xmlhttpRequest
- // @grant unsafeWindow
- // @grant GM_addElement
- // @grant GM.xmlHttpRequest
- // @connect googleapis.com
- // @namespace https://docs.google.com/
- // @match https://docs.google.com/forms/*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=google.com
- // ==/UserScript==
-
- "use strict";
-
- let apiKey = localStorage.getItem("apiKey");
- let isOldUser = localStorage.getItem("old_user");
-
- while (!apiKey || apiKey.length <= 10) {
- apiKey = window.prompt(
- "Please enter your API key. To get one for free goto 'https://makersuite.google.com/app/apikey' and paste your api key here."
- );
-
- if (apiKey == null) {
- console.log(apiKey);
- window.open("https://makersuite.google.com/app/apikey", "_blank");
- } else {
- localStorage.setItem("apiKey", apiKey);
- }
- }
-
- const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=${apiKey}`;
-
- class Question {
- #headers = {
- "Content-Type": "application/json",
- };
-
- #onerror = (error) => {
- console.warn(": Some error occured while sending request", error);
- };
-
- constructor(
- question, // (string)
- questionImage, // (string)(url)
- options, // (Array[{}])
- isOptional, // (boolean)
- questionType, // (string) textbox, multipleChoice(same for checkbox)
- htmlNode // (HTMLElement)
- ) {
- this.question = question;
- this.questionImage = questionImage;
- this.options = options;
- this.isOptional = isOptional;
- this.questionType = questionType;
- this.aiAnswer = null;
-
- if (!unsafeWindow.deleteNode) {
- this.htmlNode = htmlNode;
- }
- }
-
- async aiAssist() {
- let data = null;
-
- if (this.questionType == "multipleChoice") {
- let finalOptions = "";
- this.options.forEach((option, index) => {
- finalOptions += option.value + "\n";
- });
-
- data = `{"contents":[{"parts":[{"text":"Choose only the one correct option for this question: Question: ${this.question} Options: ${finalOptions}.\n"}]}]}`;
- } else if (this.questionType == "checkbox") {
- let finalOptions = "";
- this.options.forEach((option, index) => {
- finalOptions += option.value + "\n";
- });
-
- data = `{"contents":[{"parts":[{"text":"Choose the correct option for this question(More than one can be true): Question: ${this.question} Options: ${finalOptions}.\n"}]}]}`;
- } else {
- data = `{"contents":[{"parts":[{"text":"Write something like a human on topic: '${this.question}'.\n Start now!"}]}]}`;
- }
-
- let request = await GM.xmlHttpRequest({
- method: "POST",
- url: url,
- headers: this.#headers,
- data,
- }).catch((error) => this.#onerror);
-
- this.aiAnswer = this.parseJSON(request);
- }
-
- async fillUp() {
- await this.aiAssist();
-
- if (this.aiAnswer?.trim() == "" || !this.aiAnswer) {
- this.htmlNode.querySelector(".ai-answer").textContent =
- "😭 Failed to fetch answers from server... ";
- } else {
- this.htmlNode.querySelector(".ai-answer").textContent = this.aiAnswer;
- }
-
- if (this.questionType == "multipleChoice") {
- let allOptions = [...this.htmlNode.querySelectorAll("label")];
-
- this.options.forEach((option, index) => {
- if (this.aiAnswer?.includes(option.value)) {
- allOptions[index].click();
- }
- });
- } else if (this.questionType == "checkbox") {
- let allOptions = [...this.htmlNode.querySelectorAll("label")];
-
- this.options.forEach((option, index) => {
- if (this.aiAnswer?.includes(option.value)) {
- allOptions[index].click();
- }
- });
- } else {
- let allTextboxes = [
- ...this.htmlNode.querySelectorAll("input[type=text], textarea"),
- ];
-
- allTextboxes.forEach((element) => {
- element.value = this.aiAnswer;
- });
- }
- }
-
- parseJSON(data) {
- let parsedAnswer = null;
-
- try {
- let parsedData = JSON.parse(data.responseText);
- parsedAnswer = parsedData?.candidates?.[0]?.content?.parts?.[0]?.text;
- } catch (e) {
- console.warn("Failed to parse to JSON.", e);
- }
-
- return parsedAnswer;
- }
- }
-
- class GoogleFormParser {
- parse() {
- let finalQuestionList = [];
-
- const googleFormTitle = document.querySelector(
- ".F9yp7e.ikZYwf.LgNcQe"
- )?.textContent;
- const googleFormDescription =
- document.querySelector(".cBGGJ.OIC90c")?.textContent;
- const questionCards = document.querySelectorAll("[jsmodel='CP1oW']");
-
- if (
- questionCards == undefined ||
- questionCards == null ||
- questionCards.length == 0
- ) {
- throw ": No questions found. Maybe this form is empty";
- }
-
- questionCards.forEach((card, index) => {
- let parsedDataArray = null;
-
- let dataParams = card.getAttribute("data-params")?.replace("%.@.", "[");
-
- if (!dataParams) {
- console.warn(
- `No data-params found for card index ${index}. So, skipping this card.`,
- card
- );
- return;
- }
-
- try {
- parsedDataArray = JSON.parse(dataParams);
- } catch (e) {
- console.warn(
- `Failed to parse obtained data-params to JSON: ${dataParams}`,
- e
- );
- return;
- }
-
- let questionImage = null;
- let question = parsedDataArray?.[0]?.[1];
- let subdivsInsideCard = card.querySelectorAll(".geS5n");
-
- if (!!subdivsInsideCard.length != 0) {
- subdivsInsideCard = [...subdivsInsideCard[0].childNodes];
- }
- subdivsInsideCard = subdivsInsideCard.filter((item) => {
- return item.tagName == "DIV";
- });
-
- // Length >= 4 means question might have image;
- if (subdivsInsideCard.length >= 4) {
- subdivsInsideCard.forEach((div) => {
- let imageTags = div.querySelectorAll("img");
-
- // Either theres no img elements or we already found URL.
- if (imageTags.length == 0 || !!questionImage) {
- return;
- }
-
- questionImage = imageTags[0]?.src;
- });
- }
-
- let questionType = null;
-
- if (card.querySelectorAll(".Yri8Nb").length != 0) {
- questionType = "checkbox";
- } else if (card.querySelectorAll(".ajBQVb").length != 0) {
- questionType = "multipleChoice";
- } else if (
- card.querySelectorAll("input[type=text], textarea").length == 1
- ) {
- questionType = "textbox";
- }
-
- let options = parsedDataArray?.[0]?.[4]?.[0]?.[1];
-
- options = options?.map((option, index) => {
- let image = null;
- if (option.length >= 6) {
- image = card
- .querySelectorAll("label")
- [index]?.querySelector("img")?.src;
- }
-
- return {
- value: option[0],
- image,
- };
- });
-
- let isOptional = parsedDataArray?.[0]?.[4]?.[0]?.[2];
-
- let finalQuestionBody = new Question(
- question,
- questionImage,
- options,
- isOptional,
- questionType,
- card
- );
-
- finalQuestionList.push(finalQuestionBody);
- });
-
- return finalQuestionList;
- }
- }
-
- (function () {
- let googleForm = new GoogleFormParser();
-
- let questions = googleForm.parse();
-
- console.log(questions);
-
- let style = document.createElement("style");
- style.textContent = `.ai-container *{margin:0;padding:0;box-sizing:border-box;}.ai-container{margin-bottom: 10px;width:100%;color:#343232;padding:8px 0;background-color:#fff;border-radius:10px;box-shadow:rgba(9,30,66,.25) 0 4px 8px -2px,rgba(9,30,66,.08) 0 0 0 1px}.ai-container .ai-footer,.ai-container .ai-header{padding:4px 16px 10px;display:flex;align-items:center;justify-content:space-between}.ai-container .ai-header .ai-title{font-weight:bolder;font-size:15px}.ai-container .ai-header ul{list-style-type:none;display:flex;align-items:center;justify-content:space-between}.ai-container .ai-header ul li{font-weight:bolder;font-size:small;padding:0 6px;cursor:pointer;transition-duration:.2s;border:2px solid transparent;margin-right:8px;border-radius:4px}.ai-container .ai-header ul li:hover{color:#fff;background-color:#ff4500}.ai-container hr{border:1px solid #42ea42}.ai-container .ai-answer{font-size:13px;padding:16px 16px 8px 16px;}.ai-container .ai-footer{padding:10px 0 0 8px;width:100%;color:orange}.ai-container .ai-footer .ai-circle{display:flex;align-items:center;justify-content:center}.ai-container .ai-footer .ai-circle li{width:15px;color:#42ea42}.ai-container .ai-footer .ai-warning{font-size:10px;width:100%}`;
- document.head.appendChild(style);
-
- questions.forEach((question) => {
- const container = document.createElement("div");
- container.className = "ai-container";
-
- const divHeader = document.createElement("div");
- divHeader.className = "ai-header";
-
- const divTitle = document.createElement("div");
- divTitle.className = "ai-title";
- divTitle.textContent = "🦕 Gemini Pro";
-
- const ul = document.createElement("ul");
-
- const liSearch = document.createElement("li");
- liSearch.id = "ai-search";
- liSearch.textContent = "SEARCH";
-
- const liCopy = document.createElement("li");
- liCopy.id = "ai-copy";
- liCopy.textContent = "COPY";
-
- const hr = document.createElement("hr");
-
- const pAnswer = document.createElement("p");
- pAnswer.className = "ai-answer";
- pAnswer.textContent = "I am working on it. Please wait....";
-
- const divFooter = document.createElement("div");
- divFooter.className = "ai-footer";
-
- const pWarning = document.createElement("p");
- pWarning.className = "ai-warning";
- pWarning.textContent =
- "*Note: Not all AI generated content are 100% accurate. Use Search feature for double check.";
-
- const divCircle = document.createElement("div");
- divCircle.className = "ai-circle";
-
- const liCircle = document.createElement("li");
-
- divHeader.appendChild(divTitle);
- divHeader.appendChild(ul);
- ul.appendChild(liSearch);
- ul.appendChild(liCopy);
-
- divFooter.appendChild(pWarning);
- divFooter.appendChild(divCircle);
- divCircle.appendChild(liCircle);
-
- container.appendChild(divHeader);
- container.appendChild(hr);
- container.appendChild(pAnswer);
- container.appendChild(divFooter);
-
- question.htmlNode.appendChild(container);
-
- let options = "";
- let questionValue = question.question;
-
- question?.options?.forEach((option) => {
- options += option.value + "\n";
- });
-
- liSearch.addEventListener("click", (e) => {
- e.preventDefault();
-
- window.open(
- "https://google.com/search?q=" + questionValue + options,
- "_blank"
- );
- });
-
- liCopy.addEventListener("click", (e) => {
- setTimeout(function () {
- liCopy.textContent = "COPY";
- }, 3000);
-
- e.preventDefault();
- navigator.clipboard.writeText(questionValue + options);
- liCopy.textContent = "COPIED";
- });
- });
-
- questions.forEach((element) => {
- element.fillUp();
- });
-
- // Add a keyboard shortcut.
-
- document.addEventListener("keydown", (e) => {
- if (e.ctrlKey && e.altKey) {
- let aiElement = document.querySelectorAll(".ai-container");
- aiElement.forEach((container) => {
- if (container.style.display != "none") {
- container.style.display = "none";
- } else {
- container.style.display = "block";
- }
- });
- }
- });
-
- if (!isOldUser) {
- alert("You can press CTRL + ALT key to hide/unhide the AI");
- localStorage.setItem("old_user", "true");
- }
- })();