您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Get attendees at a google meet and do different things.
当前为
// ==UserScript== // @name Google Meet Attendees & Breakout Rooms // @namespace Google Meet Attendees & Breakout Rooms by Daniel & Emrik // @include https://meet.google.com/* // @grant none // @version 0.2.5 // @author Daniel & Emrik <[email protected]> // @description Get attendees at a google meet and do different things. // @run-at document-idle // ==/UserScript== // Changelog /* 0.2.5 Fixed attendees in sidebar mode 0.2.4 Fixed attendees not refreshing and added failsafe for attendees div 0.2.3 Fixed top margin 0.2.2 Fixed bug in old meet 0.2.1 Small bugfix 0.2.0 Now works in the new google meet layout 0.1.18 Fixed attendees not refreshing 0.1.17 Fixed button not appearing 0.1.16 Removed unnecessary premission: tabs 0.1.15 Added better language selector 0.1.14 Fixed Italian and German 0.1.13 Italian translation by Lorenzo Fabbri 0.1.12 Added support for Google's Grid View Fixed clean comparison list Fixed typo in 0.1.11 0.1.11 Fixed include yourself 0.1.10 Added donate button Disabled link shortener by default. 0.1.9 Added setting for link shortener Fixed to make sure it work when people have (,) in the name Fixed save list funciton 0.1.8 Reversed some 0.1.7 changes 0.1.7 Fixed 0.1.6 to make it work consistently 0.1.6 Quick fix for Google meet change 0.1.5 Fixed compatibility with "Meet Attendance" 0.1.4 Fixed layout on 720p monitors Fixed not on list bug 0.1.3 Added reset meets button 0.1.2 Added option to use comparison list in group generator Added swap in group generator Fixed bug if you had an incorrect attendees list 0.1.1 Fixed bug that occurred if grid view loaded after and it was the first time using the extension Fixed font size Added force english option Made group display scrollable 0.1.0 Added breakout/group rooms Notice when grid view isn't installed 0.0.4 Removed more unnessary characters in clean function Matches name reversed as well 0.0.3 Dark mode Now works when "Only show participants with video" is enabled Added people counter when comparing lists. Ex 2/32 persons here 0.0.2 Fixed Chrome extension localisation 0.0.1 Initial Release // TODO (ordered by difficulty (easiest first)) Have alternative for lookup links (g-suite only) Show last name in group generator Better layout on all screen Use chrome sync storage instead of localstorage. Add better pop-up for random person Save and load groups Aliases for names Get attendees in a better way */ const T = (untranslatedMsg) => { if (forceLanguage && forceLanguage != "auto") { if (translations[untranslatedMsg][forceLanguage]) { return translations[untranslatedMsg][forceLanguage] } else { return translations[untranslatedMsg]["en"] } } else { let languages = navigator.languages for(let i = 0; i < languages.length; i++){ let result = translations[untranslatedMsg][languages[i]] if(result != null){ return result } } return translations[untranslatedMsg]["en"] // English is default } } /* Translation guidelines: Try to keep the translation short (preferebly not much longer then the english translation). Make sure to use correct case if applicable */ /* Credits: Deutsch: Daniel and Filip Italian: Lorenzo Fabbri Feel free to submit pull requests with you translation, or send them by email if it seems hard, and I will do it for you :) */ const translations = { "hide list": { en: "▲ Hide list", sv: "▲ Göm lista", de: "▲ Liste ausblenden", it: "▲ Nascondi lista" }, "show list": { en: "▼ Show list", sv: "▼ Visa lista", de: "▼ Liste anzeigen", it: "▼ Mostra lista" }, "include yourself": { en: "Include yourself", sv: "Inkludera dig själv", de: "Schließen Sie sich ein", it: "Includi te stesso" }, "sort by last name": { en: "Sort by last name", sv: "Sortera efter efternamn", de: "Nach Nachnamen sortieren", it: "Ordina per cognome" }, "include not on list": { en: "Include people not on comparison list", sv: "Inkludera folk som inte är på jämförelselistan", de: "Personen einschließen, die nicht auf der Vergleichsliste stehen", it: "Includi persone non presenti nell'elenco di confronto" }, "close": { en: "Close", sv: "Stäng", de: "Schließen", it: "Chiudi" }, "persons": { en: "persons", sv: "personer", de: "Personen", it: "persone" }, "update list": { en: "Update list", sv: "Uppdatera listan", de: "Aktualisieren Liste", it: "Aggiorna lista" }, "copy list": { en: "Copy list", sv: "Kopiera lista", de: "Kopieren Liste", it: "Copia lista" }, "randomize person": { en: "Randomize person", sv: "Slumpa person", de: "Zufällige Person", it: "Persona casuale" }, "show comparison list": { en: "◄ Show comparison list", sv: "◄ Visa jämföringslista", de: "◄ Vergleichsliste anzeigen", it: "◄ Mostra lista di confronto" }, "hide comparison list": { en: "► Hide comparison list", sv: "► Göm jämföringslista", de: "► Vergleichsliste ausblenden", it: "► Nascondi lista di confronto" }, "show group generator": { en: "◄ Show group generator", sv: "◄ Visa grupp skapande", de: "◄ Gruppenerstellung anzeigen", it: "◄ Mostra generatore di gruppi" }, "hide group generator": { en: "► Hide group generator", sv: "► Göm grupp skapande", de: "► Gruppenerstellung ausblenden", it: "► Nascondi generatore di gruppi" }, "Insert comparison list": { en: "Insert comparison list", sv: "Kopiera in jämföringslista", de: "Vergleichsliste einfügen", it: "Inserisci lista di confronto" }, "clean comparison list": { en: "Clean comparison list", sv: "Städa jämföringslistan", de: "Liste reinigen", it: "Pulisci lista" }, "class": { en: "Class", sv: "Klass", de: "Gruppe", it: "Classe" }, "save list": { en: "Save list", sv: "Spara lista", de: "Liste speichern", it: "Salva lista" }, "load list": { en: "Load list", sv: "Ladda lista", de: "Liste laden", it: "Carica lista" }, "remove list": { en: "Remove list", sv: "Ta bort lista", de: "Gruppe entfernen", it: "Rimuovi lista" }, "result:": { en: "Result:", sv: "Resultat:", de: "Ergebnis:", it: "Risultato" }, "click on compare": { en: "Click on compare", sv: "Klicka på jämför", de: "Klicken Sie auf Vergleichen", it: "Clicca su confronta" }, "copy for chat": { en: "Copy for chat", sv: "Kopiera för chatten", de: "Kopie für den Chat", it: "Copia per la chat" }, "compare attendees": { en: "Compare Attendees", sv: "Jämför deltagare", de: "Teilnehmer vergleichen", it: "Confronta i partecipanti" }, "compare": { en: "Compare", sv: "Jämför", de: "Vergleichen", it: "Confronta" }, "attendance": { en: "Attendance", sv: "Närvaro", de: "Anwesenheit", it: "Presenza" }, "settings": { en: "Settings", sv: "Inställningar", de: "Einstellung", it: "Impostazioni" }, "dark mode": { en: "Dark mode", sv: "Mörkt läge", de: "Dunkles Design", it: "Modalità scura" }, "sort compare list by status": { en: "Sort compare list by status", sv: "Sortera jämföringslistan efter status", de: "Sortierliste nach Status sortieren", it: "Ordina elenco di confronto per stato" }, "create groups": { en: "Create groups", sv: "Skapa grupper", de: "Gruppen erstellen", it: "Crea gruppi" }, "users per group": { en: "Users / group", sv: "Användare / grupp", de: "Benutzer / Gruppe", it: "Utenti / gruppo" }, "number of groups": { en: "Number of groups", sv: "Antal grupper", de: "Anzahl der Gruppen", it: "Numero di gruppi" }, "generate groups": { en: "Generate groups", sv: "Generera grupper", de: "Gruppen generieren", it: "Genera gruppi" }, "copy groups": { en: "Copy groups", sv: "Kopiera grupper", de: "Gruppen kopieren", it: "Copia gruppi" }, "copy meets": { en: "Copy Meets-links", sv: "Kopiera Meet-länkarna", de: "Meeting-Links kopieren", it: "Copia i link-Meet" }, "force language ": { en: "Force language (reload required)", sv: "Tvinga språk (omladdning krävs)", it: "Forza lingua (richiede riavvio)" }, "use comparison list": { en: "Use comparison list", sv: "Använd jämförelselistan", it: "Usa lista di confronto" }, "reset meets":{ en: "Reset Meet-links", sv: "Nollställ Meet-länkarna", it: "Resetta i link-Meet" }, "new meet link warning":{ en: "Press \"Generate groups\" to use the new Meet links", sv: "Tryck på \"Generera grupper\" för att använda de nya Meet-länkarna", it: "Premi \"Genera gruppi\" per utilizzare i nuovi link-meet" }, "shorten link":{ en: "Shorten links (g.co)", sv: "Förkorta länkar (g.co)", it: "Abbrevia i link (g.co)" }, "use built in grid view":{ en: "Use Google's Grid View", sv: "Använd Googles Grid View", it: "Usa la vista a griglia di Google" } } // Add you in your language let youArray = ["You","Du"] // localstorage test try { localStorage.setItem("test", "hello") localStorage.removeItem("test") } catch(e) { alert("localStorage is required for the extension to function correctly") } // Declare all global variables let resultHeader = null let settingsMenu = null let peopleList = null let peopleCounter = null let yourName = null let savedClasses = null if (localStorage.getItem("gma-class-options") && localStorage.getItem("gma-class-options") != "[object Object]") { try { savedClasses = JSON.parse(localStorage.getItem("gma-class-options")) } catch(e) { console.log(e) savedClasses = JSON.parse("{}") localStorage.removeItem("gma-class-options") } } else { savedClasses = JSON.parse("{}") } let pos1, pos2, pos3, pos4 = 0 let forceLanguage = "auto" if (localStorage.getItem("gma-force-language") !== null) { forceLanguage = localStorage.getItem("gma-force-language") } if (localStorage.getItem("gmca-attendees-list") && (localStorage.getItem("gmca-attendees-list")[0] != "[")) { localStorage.setItem("gmca-attendees-list", JSON.stringify(localStorage.getItem("gmca-attendees-list").split(","))) } // Css for our project because we couldn't use a seperate css file. const s = document.createElement("style") s.innerText = ` .__gma-old-meet:hover { z-index: 8; background-color: var(--gm-neutral-highlight-color) } .__gma-button { overflow: visible } .__gma-old-meet:hover>#attendees-list-gma { display: flex } .__gma-new-meet { padding: 12px; } .__gma-new-meet:hover { z-index: 8; background-color: #28292C } #attendees-list-gma label { display: block; line-height: 24px; color: #999 } #attendees-list-gma label:not(.disabled) { cursor: pointer; color: #000 } #attendees-list-gma label small { display: block; line-height: 12px; font-weight: 400 } #attendees-list-gma hr { border: 0; height: 1px; background: #f1f3f4 } #attendees-list-gma a { box-shadow: -3px -3px 3px 0 rgba(255, 255, 255, 0.384), 3px 3px 3px 0 rgba(0, 0, 0, .09); display: inline-block; border-radius: 2rem; color: var(--gm-body-text-color); cursor: pointer; background-color: gainsboro; padding: 0 10px; margin: 5px 2px; height: 34px } #attendees-list-gma #update { display: inline-block; width: 34px; height: 34px; border-radius: 2rem 0 0 2rem; background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-244 -244 956 956"><path d="M67 288H0v160l47-44a256 256 0 0 0 209 108c131 0 238-98 254-224h-65a192 192 0 0 1-350 73l79-73zM256 0C125 0 18 98 2 224h65a192 192 0 0 1 353-68l-68 68h160V64l-46 46A256 256 0 0 0 256 0zm0 0"/></svg>'); background-repeat: no-repeat; background-position: center center; background-color: gainsboro } #attendees-list-gma #update:hover, #attendees-list-gma #show-list:hover, #attendees-list-gma a:hover { background-color: #f1f3f4 } #attendees-list-gma textarea, #generated-groups { width: calc(258px - 15px); /* this seems to break the layout?*/ resize: none; border: 3px gainsboro solid; border-radius: 6px } #attendees-list-gma textarea { height: 20vh; max-height: 300px; } #attendees-div textarea { height: 35vh; max-height: 525px; } #classSave, #cleanCompareList { float: right; } #removeClass { position: absolute; right: 20px; } #chooseClass { height: 21px; } #classInput , #chooseClass{ width: 50%; margin: 10px 0; } #attendees-list-gma #show-list { display: inline-flex; border-radius: 0 2rem 2rem 0; height: 34px; position: absolute; background-color: gainsboro; padding: 0 9px; line-height: 34px } #attendees-list-gma #settingsButton { background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M13.85 22.25h-3.7c-.74 0-1.36-.54-1.45-1.27l-.27-1.89c-.27-.14-.53-.29-.79-.46l-1.8.72c-.7.26-1.47-.03-1.81-.65L2.2 15.53c-.35-.66-.2-1.44.36-1.88l1.53-1.19c-.01-.15-.02-.3-.02-.46 0-.15.01-.31.02-.46l-1.52-1.19c-.59-.45-.74-1.26-.37-1.88l1.85-3.19c.34-.62 1.11-.9 1.79-.63l1.81.73c.26-.17.52-.32.78-.46l.27-1.91c.09-.7.71-1.25 1.44-1.25h3.7c.74 0 1.36.54 1.45 1.27l.27 1.89c.27.14.53.29.79.46l1.8-.72c.71-.26 1.48.03 1.82.65l1.84 3.18c.36.66.2 1.44-.36 1.88l-1.52 1.19c.01.15.02.3.02.46s-.01.31-.02.46l1.52 1.19c.56.45.72 1.23.37 1.86l-1.86 3.22c-.34.62-1.11.9-1.8.63l-1.8-.72c-.26.17-.52.32-.78.46l-.27 1.91c-.1.68-.72 1.22-1.46 1.22zm-3.23-2h2.76l.37-2.55.53-.22c.44-.18.88-.44 1.34-.78l.45-.34 2.38.96 1.38-2.4-2.03-1.58.07-.56c.03-.26.06-.51.06-.78s-.03-.53-.06-.78l-.07-.56 2.03-1.58-1.39-2.4-2.39.96-.45-.35c-.42-.32-.87-.58-1.33-.77l-.52-.22-.37-2.55h-2.76l-.37 2.55-.53.21c-.44.19-.88.44-1.34.79l-.45.33-2.38-.95-1.39 2.39 2.03 1.58-.07.56a7 7 0 0 0-.06.79c0 .26.02.53.06.78l.07.56-2.03 1.58 1.38 2.4 2.39-.96.45.35c.43.33.86.58 1.33.77l.53.22.38 2.55z"></path><circle cx="12" cy="12" r="3.5"></circle></svg>'); display: inline-block; position: absolute; text-align: center; right: 0; padding: 5px; height: auto; width: 24px; height: 24px; } #settingsButton svg { display: flex; justify-content: center; align-items: center } #attendees-list-gma #settingsMenu { position: absolute; text-align: center; top: 100px; left: -100px; background-color: white; z-index: 3; border: 3px #2196F3 solid; border-radius: 8px; } #attendees-list-gma #settingsMenu label { text-align: left; margin: 2px 4px 0 4px; } #attendees-list-gma #settingsHeader { padding: 5px; cursor: move; background-color: #2196F3; color: white } #attendees-list-gma { box-sizing: border-box; display: none; padding: 15px 20px 20px 0; background: white; text-align: left; cursor: auto; line-height: 36px; flex-direction: row-reverse; position: absolute; right: -232px; border-radius: 0 0 8px 8px; z-index: 7; top: 48px; max-height: 80vh; white-space: pre-wrap; } #attendees-list-gma > * { position: relative; width: 258px } .__gma-new-meet > div#attendees-list-gma { top: calc(-40vh - 320px); right: -210px; color: black; border-radius: 8px 0 0 8px; } #compare-div, #create-groups-div { padding: 0 20px 5px 20px; border-right: 3px dashed gainsboro } #compare-result-list { display: block; } #create-groups-grid { display: grid; grid-template-columns: repeat(2,1fr); grid-template-areas: "type-of-group group-number-selector" "make-group-button make-group-button" "use-comp-list use-comp-list" "generated-groups generated-groups" "copy-generated-groups copy-generated-groups" "copy-generated-meets copy-generated-meets" "reset-meets reset-meets"; grid-template-rows: auto auto auto auto; } #type-of-group { grid-area: type-of-group; border-radius: 1.1rem; background-color: #efefef; } #type-of-group > * { text-align: center; border-radius: 2rem; font-size: 0.8rem; padding: 0 7px 0 7px; } #create-groups-grid .selected { background-color: #2196F3; color: white; } #group-number-selector { width: 50%; height: 50%; display: flex; justify-self: center; align-self: center; grid-area: group-number-selector; } #make-group-button { grid-area: make-group-button; text-align: center; width: fit-content; justify-self: center; } #generated-groups { grid-area: generated-groups; width: auto; height: 35vh; max-height: 400px; overflow-y: scroll; display: block; border-collapse: collapse; } /* #generated-groups tr > *:not(:first-child) { border-left: gainsboro 1px solid; } */ #generated-groups tr th { text-align: center; font-size: 1.2rem; } #generated-groups tr td { text-align: center; } #generated-groups tr th a { padding: 0; width: 2.3rem; height: 2.3rem; } #copy-generated-groups, #copy-generated-meets, #use-comp-list, #reset-meets { text-align: center; width: fit-content; justify-self: center; } #copy-generated-groups { grid-area: copy-generated-groups; } #copy-generated-meets { grid-area: copy-generated-meets; } #use-comp-list { grid-area: use-comp-list; } #reset-meets { grid-area: reset-meets; } #attendees-div { padding-left: 20px } #attendees-list-gma h1 { font-size: 1.75rem; } #attendees-list-gma h2 { font-size: 1.3rem; } #attendees-list-gma h3 { font-size: 1rem; } h1, h2, h3, #attendees-list-gma p { margin: 0 } .rKOYsc { z-index: 0 !important; } #attendees-list-gma.dark_mode, #attendees-list-gma.dark_mode #settingsMenu, #attendees-list-gma.dark_mode #settingsMenu label { background-color: #181e23; color: white; } #attendees-list-gma.dark_mode #settingsMenu { border-color: #2196f324; } #attendees-list-gma.dark_mode #settingsHeader { background-color: #2196f324; } #attendees-list-gma.dark_mode #update { background-image: url('data:image/svg+xml;utf8,<svg fill="%23FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="-244 -244 956 956"><path d="M67 288H0v160l47-44a256 256 0 0 0 209 108c131 0 238-98 254-224h-65a192 192 0 0 1-350 73l79-73zM256 0C125 0 18 98 2 224h65a192 192 0 0 1 353-68l-68 68h160V64l-46 46A256 256 0 0 0 256 0zm0 0"/></svg>'); } #attendees-list-gma.dark_mode #settingsButton { background-image: url('data:image/svg+xml;utf8,<svg fill="%23FFFFFF" xmlns="http://www.w3.org/2000/svg" viewBox="-4 -4 32 32"><path d="M13.85 22.25h-3.7c-.74 0-1.36-.54-1.45-1.27l-.27-1.89c-.27-.14-.53-.29-.79-.46l-1.8.72c-.7.26-1.47-.03-1.81-.65L2.2 15.53c-.35-.66-.2-1.44.36-1.88l1.53-1.19c-.01-.15-.02-.3-.02-.46 0-.15.01-.31.02-.46l-1.52-1.19c-.59-.45-.74-1.26-.37-1.88l1.85-3.19c.34-.62 1.11-.9 1.79-.63l1.81.73c.26-.17.52-.32.78-.46l.27-1.91c.09-.7.71-1.25 1.44-1.25h3.7c.74 0 1.36.54 1.45 1.27l.27 1.89c.27.14.53.29.79.46l1.8-.72c.71-.26 1.48.03 1.82.65l1.84 3.18c.36.66.2 1.44-.36 1.88l-1.52 1.19c.01.15.02.3.02.46s-.01.31-.02.46l1.52 1.19c.56.45.72 1.23.37 1.86l-1.86 3.22c-.34.62-1.11.9-1.8.63l-1.8-.72c-.26.17-.52.32-.78.46l-.27 1.91c-.1.68-.72 1.22-1.46 1.22zm-3.23-2h2.76l.37-2.55.53-.22c.44-.18.88-.44 1.34-.78l.45-.34 2.38.96 1.38-2.4-2.03-1.58.07-.56c.03-.26.06-.51.06-.78s-.03-.53-.06-.78l-.07-.56 2.03-1.58-1.39-2.4-2.39.96-.45-.35c-.42-.32-.87-.58-1.33-.77l-.52-.22-.37-2.55h-2.76l-.37 2.55-.53.21c-.44.19-.88.44-1.34.79l-.45.33-2.38-.95-1.39 2.39 2.03 1.58-.07.56a7 7 0 0 0-.06.79c0 .26.02.53.06.78l.07.56-2.03 1.58 1.38 2.4 2.39-.96.45.35c.43.33.86.58 1.33.77l.53.22.38 2.55z"></path><circle cx="12" cy="12" r="3.5"></circle></svg>') } #attendees-list-gma.dark_mode > * { border-color: #0000006b; } #attendees-list-gma.dark_mode a, #attendees-list-gma.dark_mode #update, #attendees-list-gma.dark_mode #show-list { background-color: #38393f; color: white; box-shadow: none; } #attendees-list-gma.dark_mode textarea, #attendees-list-gma.dark_mode input, #attendees-list-gma.dark_mode select, #attendees-list-gma.dark_mode select option { color: white; background-color: #181e23; border-color: #0000003d; } #attendees-list-gma.dark_mode #type-of-group { background-color: #38393f; } #attendees-list-gma.dark_mode #generated-groups { border: 3px #0000003d solid; } #attendees-list-gma.dark_mode #create-groups-grid .selected { background-color: #ffffff70; } ::placeholder { color: lightgrey; opacity: 1; } ` document.body.append(s) var newmeet = false var useOldClassSelector = true setInterval(() => { newmeet = false useOldClassSelector = true let screencast = document.querySelectorAll("[data-fps-request-screencast-cap]") var buttons if (!(screencast.length == 0)){ buttons = screencast[screencast.length-1].parentElement.parentElement.parentElement } else if(document.getElementsByClassName("NzPR9b").length != 0) { screencast = document.getElementsByClassName("NzPR9b") buttons = screencast[0] useOldClassSelector = false } else { newmeet = true buttons = document.querySelector("div.SGP0hd.kunNie") } if ((buttons) && (document.getElementsByClassName("__gma-button").length == 0)) { console.log("%c Initialized Attendees Script", "background: #FFFFFF; color: #242424") if (useOldClassSelector) { buttons.prepend(buttons.children[3].cloneNode()) } else { buttons.prepend(buttons.children[1].cloneNode()) } const toggleButton = document.createElement("div") toggleButton.classList = buttons.children[3].classList toggleButton.classList.add("__gma-button") if (newmeet) { toggleButton.classList.add("__gma-new-meet") } else { toggleButton.classList.add("__gma-old-meet") } if (toggleButton.classList.contains("__gmgv-button")) { toggleButton.classList.remove("__gmgv-button") } toggleButton.style.display = "flex" toggleButton.onclick = () => { let elem = document.getElementById("attendees-list-gma") if (elem.__pinned) { elem.style.display = null elem.__pinned = false } else { elem.style.display = "flex" elem.__pinned = true document.firstElementChild.onclick = (event) => { if (event.target.innerText == "Hide Participant") { let elem = document.getElementById("attendees-list-gma") elem.style.display = null elem.__pinned = false document.firstElementChild.onclick = "" } } } } buttons.prepend(toggleButton) // Adds a icon to item bar const toggleButtonSVG = document.createElementNS("http://www.w3.org/2000/svg", "svg") toggleButtonSVG.id = "icon" toggleButtonSVG.style.width = "24px" toggleButtonSVG.style.height = "24px" toggleButtonSVG.setAttribute("viewBox", "0 0 24 24") toggleButtonSVG.innerHTML = "<path fill=\"currentColor\" d=\"M11.41 3.76c-.8.04-1.6.31-2.27.86-1.5 1.22-1.89 3.52-.59 5.06 1.06 1.24 3.02 1.55 4.3.4.98-.88 1.21-2.5.2-3.52a2.05 2.05 0 00-1.34-.6c-.5-.02-1.05.17-1.42.61-.23.29-.35.64-.33 1.01.01.38.23.82.65 1.01.32.14.52.1.78-.01a.7.7 0 00.39-.37.74.74 0 00-.07-.65l-.84.54a.41.41 0 01-.01-.3c.05-.11.12-.13.13-.14l.04.02c-.07-.03-.07-.04-.07-.14s.05-.27.1-.33a.67.67 0 01.6-.25c.24.02.51.13.69.3.56.57.41 1.55-.18 2.08-.82.74-2.14.53-2.85-.3-.92-1.09-.63-2.76.45-3.64 1.34-1.09 3.37-.73 4.42.6 1.25 1.6.82 3.98-.77 5.2l.61.79a4.73 4.73 0 00.94-6.6 4.31 4.31 0 00-3.56-1.63zm.44 9.55c-1.42 0-3.45.34-5.19 1.04-.87.35-1.67.79-2.28 1.35a2.9 2.9 0 00-1.03 2.11v3.5h17v-3.5a2.9 2.9 0 00-1.04-2.11c-.6-.56-1.4-1-2.27-1.35a15.08 15.08 0 00-5.2-1.04zm0 1c1.25 0 3.22.33 4.81.97.8.32 1.5.72 1.97 1.15.48.44.72.89.72 1.38v2.5h-15v-2.5c0-.5.24-.94.71-1.38a6.57 6.57 0 011.97-1.15c1.6-.64 3.57-.97 4.82-.97zm0 1c-1.43 0-2.92.34-4.11.77-.6.21-1.11.45-1.51.7-.4.25-.74.45-.86.9l-.02.08v1.55h13v-1.57l-.02-.06c-.13-.47-.46-.66-.87-.9-.4-.25-.91-.49-1.5-.7a12.56 12.56 0 00-4.11-.77zm0 1c1.27 0 2.68.31 3.77.7.54.2 1 .42 1.32.62.3.19.42.38.4.3v.38h-11v-.37c0 .07.1-.12.41-.3.32-.2.79-.42 1.33-.62 1.09-.4 2.5-.7 3.77-.7z\"></path>" toggleButton.appendChild(toggleButtonSVG) // Creates the main div for every element const additionalOptions = addElement("div",toggleButton,"attendees-list-gma",null) additionalOptions.onclick = e => e.stopPropagation() const seeAttendeesDiv = addElement("div",additionalOptions,"attendees-div",null) additionalOptions.onmouseout = () => { // Change to screencast const variable document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "8px" } additionalOptions.onmouseover = () => { if (compare.style.display !== "none") { document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "0" } } addElement("h1",seeAttendeesDiv,null,T("attendance")) const updateListI = addElement("a",seeAttendeesDiv,"update",null) updateListI.onclick = getAllAttendees const showListI = addElement("a",seeAttendeesDiv,"show-list",T("hide list")) showListI.onclick = (e) => { if (peopleList.style.display === "none") { peopleList.style.display = "flex" e.target.innerText = T("hide list") } else { peopleList.style.display = "none" e.target.innerText = T("show list") } } const settings = addElement("a",seeAttendeesDiv,"settingsButton",null) settings.onclick = () => { showElement(document.getElementById("settingsMenu")) } settingsMenu = addElement("div",seeAttendeesDiv,"settingsMenu",null) settingsMenu.style.display = "none" const settingsHeader = addElement("div",settingsMenu,"settingsHeader",T("settings")) settingsHeader.style.display = "block" settingsHeader.onmousedown = (even) => { movableDiv(even, "settingsMenu") } // Calls addSetting function addSetting("gma-include-yourself", T("include yourself")) addSetting("gma-sort-by-last-name",T("sort by last name")) addSetting("gma-add-not-on-list",T("include not on list")) // addSetting("gma-more-letters",T("maximize letters")) addSetting("gma-sort-on-compare",T("sort compare list by status")) addSetting("gma-shorten-link",T("shorten link")) addSetting("gma-original-grid-view",T("use built in grid view")) let forceLanguageParent = addElement("label", settingsMenu, null, T("force language ")) let forceLanguageElem = document.createElement("select") let forceLanguageOptions = ["Auto","English","Swedish","Italian","German"] let forceLanguageValue = ["auto","en","sv","it","de"] for(let i = 0; i < forceLanguageOptions.length; i++) { let el = document.createElement("option") el.textContent = forceLanguageOptions[i] el.value = forceLanguageValue[i] forceLanguageElem.appendChild(el) } forceLanguageElem.value = forceLanguage forceLanguageElem.onchange = e => { localStorage.setItem("gma-force-language", e.target.value) forceLanguage = e.target.value } forceLanguageParent.prepend(forceLanguageElem) const darkModeParent = addElement("label", settingsMenu, null,T("dark mode")) const darkMode = document.createElement("input") darkMode.type = "checkbox" if (localStorage.getItem("gma-dark-mode") === "true") { darkMode.checked = true additionalOptions.className = "dark_mode" } darkMode.onchange = e => { localStorage.setItem("gma-dark-mode", e.target.checked) additionalOptions.classList.toggle("dark_mode") } darkModeParent.prepend(darkMode) const closeSettings = addElement("a",settingsMenu,null,T("close")) closeSettings.onclick = () => { showElement(document.getElementById("settingsMenu")) } peopleCounter = document.createElement("p") peopleList = document.createElement("textarea") peopleList.readOnly = true // peopleList.rows = 20 let attendees = localStorage.getItem("gmca-attendees-list") if (attendees) { // peopleList.value = attendees.replace(/,/g, String.fromCharCode(13, 10)) peopleList.value = JSON.parse(attendees).join(String.fromCharCode(13, 10)) peopleCounter.innerText = (attendees.length - attendees.replace(/,/g, "").length + 1) + " " + T("persons") } else { peopleList.value = T("update list") peopleCounter.innerText = "0 " + T("persons") } peopleList.style.display = "block" seeAttendeesDiv.appendChild(peopleList) seeAttendeesDiv.appendChild(peopleCounter) const copyList = addElement("a",seeAttendeesDiv,null,T("copy list")) copyList.onclick = () => { navigator.clipboard.writeText(JSON.parse(localStorage.getItem("gmca-attendees-list")).join("\n")) } const randomPerson = addElement("a",seeAttendeesDiv,null,T("randomize person")) randomPerson.onclick = () => { let attendees = JSON.parse(localStorage.getItem("gmca-attendees-list")) setTimeout(() => { // to make it async alert(attendees[Math.floor(Math.random() * attendees.length)]) }, 1) } const showCompareList = addElement("a",seeAttendeesDiv,null,T("show comparison list")) showCompareList.onclick = (e) => { if (compare.style.display === "none") { compare.style.display = "block" e.target.innerText = T("hide comparison list") additionalOptions.style.borderRadius = "8px 0 8px 8px" document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "0" } else { compare.style.display = "none" e.target.innerText = T("show comparison list") additionalOptions.style.borderRadius = "0 0 8px 8px" document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "8px" } } const showCreateGroups = addElement("a",seeAttendeesDiv,null,T("show group generator")) showCreateGroups.onclick = (e) => { if (createGroups.style.display === "none") { createGroups.style.display = "block" e.target.innerText = T("hide group generator") additionalOptions.style.borderRadius = "8px 0 8px 8px" document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "0" } else { createGroups.style.display = "none" e.target.innerText = T("show group generator") additionalOptions.style.borderRadius = "0 0 8px 8px" document.getElementsByClassName("NzPR9b")[0].style.borderBottomLeftRadius = "8px" } } const compare = addElement("div",additionalOptions,"compare-div",null) compare.style.display = "none" addElement("h2",compare,null,T("compare attendees")) const compareList = addElement("textarea",compare,"compare-list",null) // compareList.rows = 10 compareList.placeholder = T("Insert comparison list") compareList.style.display = "block" const compareButton = addElement("a",compare,null,T("compare")) compareButton.onclick = compareLists const cleanCompare = addElement("a",compare,"cleanCompareList",T("clean comparison list")) cleanCompare.onclick = cleanCompareLists const classInput = addElement("input",compare,"classInput",null) classInput.attributes["type"] = "text" classInput.placeholder = T("class") classInput.autocomplete = "off" const saveButton = addElement("a",compare,"classSave",T("save list")) saveButton.onclick = saveClass const chooseClass = addElement("select",compare,"chooseClass",null) const defaultClassOption = addElement("option",chooseClass,null,T("load list")) chooseClass.onchange = (selectedClass) => { Object.entries(savedClasses).forEach(className => { if (className[0] == selectedClass.target.selectedOptions[0].value){ document.getElementById("compare-list").value = className[1].join(String.fromCharCode(13, 10)) } if (defaultClassOption.selected) { document.getElementById("compare-list").value = "" } }) } if (savedClasses) { Object.keys(savedClasses).forEach(className => { let chooseClassOptions = document.createElement("option") chooseClassOptions.innerText = className chooseClass.appendChild(chooseClassOptions) }) } const removeClass = addElement("a",compare,"removeClass",T("remove list")) removeClass.onclick = () => { let classElement = document.getElementById("chooseClass") let className = classElement.selectedOptions[0].value if (className != T("load list")) { classElement.removeChild(classElement.selectedOptions[0]) } classElement.firstChild.selected = true document.getElementById("compare-list").value = null removeClassName(className) } resultHeader = addElement("h3",compare,"resultHeader",T("result:")) const compareResultList = addElement("textarea",compare,"compare-result-list",null) // compareResultList.rows = 10 compareResultList.readOnly = true compareResultList.value = T("click on compare") const copyCompareList = addElement("a",compare,null,T("copy list")) copyCompareList.onclick = () => { navigator.clipboard.writeText(compare.children[compare.childElementCount-3].value) } const copyCompareListForChat = addElement("a",compare,null,T("copy for chat")) copyCompareListForChat.onclick = () => { let toCopy = compare.children[compare.childElementCount-3].value if (toCopy.length > 500) { toCopy = getShortName(toCopy.split("\n"), true).join("\n") if (toCopy.length > 500) { toCopy = toCopy.split("\n").map(elem => elem.slice(0, -1)).join("\n") if (toCopy.length > 500) { toCopy = toCopy.replace(/\./g, "") } } // if (localStorage.getItem("gma-more-letters") === "true") { // while (toCopy.length > 500) { // toCopy = toCopy.split("\n").map(elem => elem.split(" ").concat("").concat("").slice(0, 3).join(" ").slice(0, -1)).join("\n") // } // } else { // toCopy = toCopy.split("\n").map((elem) => { // let spacePosition = elem.indexOf(" ", 3) // if (spacePosition > 0) { // return elem.substring(0, spacePosition+2) // } else { // return elem // } // }).join("\n") // } while (toCopy.length > 500) { toCopy = toCopy.split("\n").map(elem => elem.slice(0, -1)).join("\n") } } navigator.clipboard.writeText(toCopy) } // group generator const createGroups = addElement("div",additionalOptions,"create-groups-div",null) createGroups.style.display = "none" // Lägg till översättning addElement("h2",createGroups,null,T("create groups")) const createGroupsGrid = addElement("div",createGroups,"create-groups-grid",null) // Choose to generate groups by number of people or number of groups const numberOfGroups = addElement("div",createGroupsGrid,"type-of-group",null) const groupsWithPeople = addElement("div",numberOfGroups,"group-members",T("users per group")) groupsWithPeople.onclick = (e) => { document.getElementById("group-number").className = "" e.target.className = "selected" } const groupsWithNumber = addElement("div",numberOfGroups,"group-number",T("number of groups")) groupsWithNumber.className = "selected" groupsWithNumber.onclick = (e) => { document.getElementById("group-members").className = "" e.target.className = "selected" } // Creates the dropdown menu const groupNumberSelector = addElement("select",createGroupsGrid,"group-number-selector",null) for (let i = 1; i < 15; i++) { addElement("option",groupNumberSelector,null,i) } const generateGroupsButton = addElement("a",createGroupsGrid,"make-group-button",T("generate groups")) generateGroupsButton.onclick = generateGroups let parent = addElement("label", createGroupsGrid, "use-comp-list", T("use comparison list")) let elem = document.createElement("input") elem.type = "checkbox" elem.checked = localStorage.getItem("gma-use-comp-list-group") === "true" elem.onchange = e => { localStorage.setItem("gma-use-comp-list-group", e.target.checked) } parent.prepend(elem) // addElement("textarea",createGroupsGrid,"generated-groups",null) addElement("table",createGroupsGrid,"generated-groups",null) if (localStorage.getItem("gma-groups") !== null) { printOutGroups(JSON.parse(localStorage.getItem("gma-groups"))) } addElement("a",createGroupsGrid,"copy-generated-groups",T("copy groups")).onclick = () => { copyGroups(JSON.parse(localStorage.getItem("gma-groups"))) } addElement("a",createGroupsGrid,"copy-generated-meets",T("copy meets")).onclick = () => { copyMeets(JSON.parse(localStorage.getItem("gma-group-meets")), JSON.parse(localStorage.getItem("gma-groups")).length) } addElement("a",createGroupsGrid,"reset-meets",T("reset meets")).onclick = () => { localStorage.removeItem("gma-group-meets") alert(T("new meet link warning")) } } }, 250) const movableDiv = (even, moveID) => { let elmnt = document.getElementById(moveID) even = even || window.event even.preventDefault() pos3 = even.clientX pos4 = even.clientY document.onmouseup = () => { document.onmouseup = null document.onmousemove = null } document.onmousemove = (ev) => { ev = ev || window.event ev.preventDefault() pos1 = pos3 - ev.clientX pos2 = pos4 - ev.clientY pos3 = ev.clientX pos4 = ev.clientY elmnt.style.top = (elmnt.offsetTop - pos2) + "px" elmnt.style.left = (elmnt.offsetLeft - pos1) + "px" } } // Removes double spaces, dots, numbers, numbers followed by a letter, empty new row and new row with space. // This is used to clean input textarea from unnecessary signs. const cleanCompareLists = () => { document.getElementById("compare-list").value = document.getElementById("compare-list").value.replace(/ {2,}|, /g, " ").replace(/\. {0,}|[0-9][a-zA-Z]|[0-9]|\n$| $|^ |\t/g, "").replace(/\n{2,}|\n | \n/g, "\n") } // Takes an element for input and displays it if its not ready displayed. const showElement = (elem) => { if (elem.style.display === "none") { elem.style.display = "block" } else { elem.style.display = "none" } } const reverseName = (name) => { let words = name.split(" ").reverse() let string = "" for(let word in words) string += (word > 0 ? " " : "") + words[word] return string } // This function compares the attendees to a class list and then outputs who is present and who is not. // Present people are marked by a green checkmark and not present people is marked by a red cross. // People that was found in the meet but not in the class list is marked by a questionmark. const compareLists = () => { let current = JSON.parse(localStorage.getItem("gmca-attendees-list")) let currentLowerCase = current.map((x) => x.toLowerCase()) let listToCompare = document.getElementById("compare-list").value.split("\n") let count = 0 let out = [] if (listToCompare[0] != "") { listToCompare.forEach(listItem => { let lowercaseName = listItem.toLowerCase() if (currentLowerCase.includes(lowercaseName)) { out.push("✔️ " + listItem) count += 1 } else if (currentLowerCase.includes(reverseName(lowercaseName))) { out.push("✔️ " + listItem) count += 1 } else { out.push("❌ " + listItem) } }) } if (current[0] != "" && localStorage.getItem("gma-add-not-on-list") === "true") { current.forEach(listItem => { if (!listToCompare.includes(listItem)) { out.push("❔ " + listItem) } }) } if (localStorage.getItem("gma-sort-on-compare") === "true") { out.sort() } if (out.length > 0) { document.getElementById("compare-result-list").value = out.join(String.fromCharCode(13, 10)) } let compareListPeoples = document.getElementById("compare-list").value.split("\n").length if (compareListPeoples > 1) { resultHeader.innerText = T("result:") + " " + count + "/" + compareListPeoples } else { resultHeader.innerText = T("result:") } } // Saves inputted class students in a array with the class name. // Then stores it in localStorage to be accessed later. const saveClass = () => { let className = document.getElementById("classInput").value let chooseClassOptions = document.createElement("option") chooseClassOptions.innerText = className let newList = savedClasses[className] == null if (newList) { document.getElementById("chooseClass").appendChild(chooseClassOptions) } savedClasses[className] = document.getElementById("compare-list").value.split("\n") localStorage.setItem("gma-class-options", JSON.stringify(savedClasses)) if (newList) { document.getElementById("chooseClass").lastChild.selected = true } else { for (const elem of document.getElementById("chooseClass").children) { if (elem.text == className) { elem.selected = true } } } } // Removes a class that you selected in localStorage const removeClassName = (className) => { savedClasses[className] = null delete savedClasses[className] localStorage.setItem("gma-class-options", JSON.stringify(savedClasses)) } // The main function used to create elements. // If there is no id or innertext supplied the function skips that. const addElement = (element, parent, id, innertext) => { let elem = document.createElement(element) if (id) { elem.id = id } if (innertext) { elem.innerText = innertext } parent.appendChild(elem) return elem } // The main function for adding new options. // It takes a path to localStorage as input and saves if the user has checked the box or not. // "name" is a variable name. const addSetting = (localStoragePath, name) => { let parent = addElement("label", settingsMenu, null, name) let elem = document.createElement("input") elem.type = "checkbox" elem.checked = localStorage.getItem(localStoragePath) === "true" elem.onchange = e => { localStorage.setItem(localStoragePath, e.target.checked) } parent.prepend(elem) } // const fetchMeet = (callback) => { // fetch("https://meet.google.com/new") // .then(res => res.text()) // .then(body => { // callback(body.match(/"https:\/\/meet.google.com\/([a-z]*-[a-z]*-[a-z]*)"/)[1]) // }) // .catch(err => {throw(err)}) // } const httpGet = (responseCallback) => { let xhr = new XMLHttpRequest() xhr.onreadystatechange = function() { if (xhr.readyState === XMLHttpRequest.DONE) { responseCallback(xhr.responseText, xhr.status) } } xhr.open('GET', 'https://meet.google.com/new', true) xhr.send() } const generateMeets = (numberOfMeets, responseCallback) => { let meets = [] let done = 0 let linkShortener = localStorage.getItem("gma-shorten-link") === "true" for (let i = 0; i < numberOfMeets; i++) { httpGet(function (response, statusCode) { if (statusCode === 200) { if(linkShortener) { meets.push("https://g.co/meet/" + (response.match(/"https:\/\/meet.google.com\/([a-z]*-[a-z]*-[a-z]*)"/)[1])) } else { meets.push("https://meet.google.com/" + (response.match(/"https:\/\/meet.google.com\/([a-z]*-[a-z]*-[a-z]*)"/)[1])) } if (++done == numberOfMeets) { responseCallback(meets, true) } } else { responseCallback(null, false) } }) } } const getShortName = (names, skipFirstName = false) => { function generateSignature(name, numberOfLetters = 1) { let parts = name.split(' ') if (skipFirstName) { if (parts.length <= 2) return name return parts.shift() + ' ' + parts.shift() + ' ' + parts.map(n => n.substring(0, numberOfLetters)).join('.') + '.' } else { if (parts.length <= 1) return name return parts.shift() + ' ' + parts.map(n => n.substring(0, numberOfLetters)).join('.') + '.' } } let nameSignatures = names.map(name => generateSignature(name)) for(let j = 2; j < 20; j++) { // 20 letters is the limit for last names let temp = Array.from(nameSignatures) let done = true for (let i = 0; i < names.length; i++) { if (temp.includes(temp.shift())) { done = false let k = i + 1 + temp.indexOf(nameSignatures[i]) nameSignatures[i] = generateSignature(names[i], j) nameSignatures[k] = generateSignature(names[k], j) } } if (done) break } return(nameSignatures) } // From: https://stackoverflow.com/questions/6274339/how-can-i-shuffle-an-array const shuffle = (a) => { for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]] } return(a) } const groupGenerator = (number, specifyPeople) => { let attendees if (localStorage.getItem("gma-use-comp-list-group") === "true") { attendees = shuffle(document.getElementById("compare-list").value.split("\n")) } else { attendees = shuffle(JSON.parse(localStorage.getItem("gmca-attendees-list"))) } attendees = getShortName(attendees) if (specifyPeople){ number = Math.floor((attendees.length) / number) } let groups = [] for(let i = 0; i < number; i++) { groups.push([]) } for (let i = 0; i < attendees.length; i++) { groups[i % number].push(attendees[i]) } localStorage.setItem("gma-groups", JSON.stringify(groups)) return(groups) } const generateGroups = () => { let number = document.getElementById("group-number-selector").value let groupsByPeople = document.getElementById("group-members").className let groupsByNumber = document.getElementById("group-number").className let groups switch("selected") { case groupsByPeople: groups = groupGenerator(number, true) printOutGroups(groups) break case groupsByNumber: groups = groupGenerator(number, false) printOutGroups(groups) break default: console.log("Group switch failed") break } } const printOutGroups = (groups) => { document.getElementById("generated-groups").innerText = "" var meets if (localStorage.getItem("gma-group-meets") != null && JSON.parse(localStorage.getItem("gma-group-meets")).length >= groups.length ) { meets = JSON.parse(localStorage.getItem("gma-group-meets")) printOutGroupsPart2(groups,meets) } else { let numberOfMeetsToGen = 0 if (localStorage.getItem("gma-group-meets") == null) { numberOfMeetsToGen = groups.length } else { numberOfMeetsToGen = groups.length - JSON.parse(localStorage.getItem("gma-group-meets")).length } generateMeets(numberOfMeetsToGen, function (meetsArr, successful) { if (successful) { if (localStorage.getItem("gma-group-meets") == null) { meets = meetsArr } else { meets = JSON.parse(localStorage.getItem("gma-group-meets")).concat(meetsArr) } localStorage.setItem("gma-group-meets", JSON.stringify(meets)) printOutGroupsPart2(groups,meets) } }) } } const printOutGroupsPart2 = (groups, meets) => { let table = document.getElementById("generated-groups") for (let i = 0; i < groups.length; i += 3) { let tableRow = addElement("tr",table,null,null) for (let j = i; j < Math.min(i + 3,groups.length); j++) { let tableHeader = addElement("th",tableRow,null,null) let meetLink = addElement("a",tableHeader,null,j+1) meetLink.href = meets[j] meetLink.target = "_blank" meetLink.rel = "noopener noreferrer" } for (let k = 0; k < groups[i].length; k++) { let tableRowGroupNames = addElement("tr",table,null,null) for (let l = i; l < Math.min(i + 3,groups.length); l++) { let tableData = addElement("td", tableRowGroupNames, null, (groups[l] ?? [])[k] ?? "") tableData.dataset.groupnum = l if (tableData.innerText != "") { tableData.draggable = true } createTableDataExtras(tableData) } } addElement("tr",table,null,null) } } const createTableDataExtras = (tableData) => { tableData.ondragstart = (ev => { ev.dataTransfer.setData("text", ev.target.innerText) }) tableData.style.cursor = "grab" tableData.ondragover = (ev => ev.preventDefault()) tableData.ondrop = (ev => { ev.preventDefault() let groups = localStorage.getItem("gma-groups") let toSwap = ev.target.innerText let newName = ev.dataTransfer.getData("text") for (const elem of document.querySelectorAll("td")) { if (elem.innerText == newName) { elem.innerText = toSwap if (toSwap == "") { elem.draggable = false let parsedGroup = JSON.parse(groups) parsedGroup[elem.dataset.groupnum] = parsedGroup[elem.dataset.groupnum].filter(e => e != newName) parsedGroup[ev.target.dataset.groupnum].push(newName) localStorage.setItem("gma-groups", JSON.stringify(parsedGroup)) } else { elem.draggable = true // This one should maybe be reworked to look more like then one above localStorage.setItem("gma-groups", localStorage.getItem("gma-groups").replace(toSwap, "🎗 PENDING SWAP 🎗").replace(newName,toSwap).replace("🎗 PENDING SWAP 🎗", newName)) } // var oldElement = elem break } } ev.target.innerText = newName ev.target.draggable = true }) } const copyGroups = (groups) => { var stringToCopy = "" for (let i = 0; i < groups.length; i++) { stringToCopy += (i+1) + ": " + groups[i].join(" ") + "\n" } navigator.clipboard.writeText(stringToCopy) } const copyMeets = (meets, groupLength) => { var stringToCopy = "" for (let i = 0; i < groupLength; i++) { stringToCopy += (i+1) + ": " + meets[i] + "\n" } navigator.clipboard.writeText(stringToCopy) } const getAllAttendees = () => { /* This is the function that should be reworked currently it forces grid view and then take all the names which is really inefficent and stupid but I don't know how to do it in a better way. :( */ // Removes duplicate students in an Array function removeDups(names) { let unique = {} names.forEach(function(i) { if(!unique[i]) { unique[i] = true } }) return Object.keys(unique) } // START This section turns on grid view for 3 seconds and grabs all the names. Then it turns itself off. let waitTime = 0 let toChange = [false, false] let checkboxes var buttons if (!newmeet) { let screencast = document.querySelectorAll("[data-fps-request-screencast-cap]") if (!(screencast.length == 0)){ buttons = screencast[screencast.length-1].parentElement.parentElement.parentElement } else { screencast = document.getElementsByClassName("NzPR9b") buttons = screencast[0] useOldClassSelector = false } let buttonChildren = buttons.children if (localStorage.getItem("gma-original-grid-view") !== "true") { for (let i = 0; i < buttonChildren.length; i++) { if (buttonChildren[i].classList.contains("__gmgv-button")) { var theButton = buttonChildren[i] break } else if (i == buttonChildren.length - 1) { alert("Grid View NOT detected, make sure you have Google Meet Grid View installed if you want to use that function. It will now use the built-in grid view from now on (change this anytime in settings)") localStorage.setItem("gma-original-grid-view", true) document.querySelector("#settingsMenu > label:nth-child(7) > input[type=checkbox]").checked = true } } checkboxes = theButton.lastElementChild.children let showOnlyVideo = checkboxes[0].firstChild.checked let gridtoggle = false if (theButton.firstElementChild.innerHTML.substring(30, 31) == "1") { gridtoggle = true } if (!gridtoggle) { theButton.click() toChange[0] = true waitTime += 3000 } if (showOnlyVideo) { checkboxes[0].firstChild.click() toChange[1] = true waitTime += 1000 } } } // END setTimeout(() => { let nameSelector = "epqixc" let divList let people = [] if (newmeet) { divList = document.querySelector("div[style='inset: 72px 16px 80px;']") if (divList == null) { divList = document.querySelector("#ow3 > div.T4LgNb > div > div:nth-child(9) > div.crqnQb > div.OKJMXc.xYDeBf > div.zWfAib.c32Trc.eFmLfc.nrxduf.a1pVef") } if (divList == null) { divList = document.querySelector("div[style='inset: 16px 16px 80px;']") } divList = divList.children } else { divList = document.getElementsByClassName(nameSelector) } for (let item of divList) { var ppl = item.innerText if (!ppl.endsWith("close_fullscreen") && !youArray.includes(ppl) && ppl != "") { people.push(ppl) } } if (people.length == 1 && people[0] == buttons.lastChild.firstChild.children[2].innerText) { people = [] } if (localStorage.getItem("gma-include-yourself") === "true") { if (yourName == null) { document.querySelectorAll("#yDmH0d > script").forEach( (elements) => { //Locked id may break later let text = elements.innerText if (text.includes('ds:8')) { people.push(text.split(",")[9].slice(1,-1)) } }) } else { people.push(yourName) } } people = people.map((name) => { if (/[0-9a-zA-Z ]{1,} \([0-9a-zA-Z ]{1,}\)$/.test(name)) { return name.substring(name.indexOf("(") + 1, name.length - 1) } else { return name } }) let attendees = removeDups(people) if (localStorage.getItem("gma-sort-by-last-name") === "true") { attendees = attendees.sort((a, b) => { if (a.split(" ").concat("")[1] > b.split(" ").concat("")[1]) { return 1 } else if (a.split(" ").concat("")[1] < b.split(" ").concat("")[1]){ return -1 } else if (a.split(" ")[0] > b.split(" ")[0]) { return 1 } else if (a.split(" ")[0] < b.split(" ")[0]) { return -1 } else { return 0 } }) // attendees = attendees.sort((a, b) => a.split(" ").pop()[0] < b.split(" ").pop()[0] ? -1 : 1) } else { attendees = attendees.sort() } localStorage.setItem("gmca-attendees-list", JSON.stringify(attendees)) peopleList.value = attendees.join(String.fromCharCode(13, 10)) peopleCounter.innerText = attendees.length + " " + T("persons") if (localStorage.getItem("gma-original-grid-view") !== "true") { setTimeout(() => { if (toChange[0]) { theButton.click() } if (toChange[1]) { checkboxes[0].firstChild.click() } }, 1000) } }, waitTime) }