diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..7afa880 Binary files /dev/null and b/icon.png differ diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..51fa703 --- /dev/null +++ b/manifest.json @@ -0,0 +1,19 @@ +{ + "name": "Puzzle leaderboard tracker", + "description": "Base Level Extension", + "version": "1.0", + "manifest_version": 3, + "action": { + "default_popup": "popup.html", + "default_icon": "icon.png" + }, + "permissions": [ + "storage" + ], + "content_scripts": [ + { + "js": ["scripts/content.js"], + "matches": ["https://meet.google.com/*"] + } + ] +} diff --git a/popup.html b/popup.html new file mode 100644 index 0000000..3f1875a --- /dev/null +++ b/popup.html @@ -0,0 +1,80 @@ + + + + +

Puzzle contest

+
+
0:00:00
+ + + + +
+
+
+
+ + diff --git a/popup.js b/popup.js new file mode 100644 index 0000000..d5a43b2 --- /dev/null +++ b/popup.js @@ -0,0 +1,110 @@ +let startTime = 0; +let endTime = 0; +let messagesCount = 0; +let showMessages = false; + +async function init() { + const data = await chrome.storage.local.get(['startTime', 'endTime']); + if (data?.endTime > 0) { + startTime = data.startTime; + endTime = data.endTime; + document.getElementById('reset-button').style.display = 'block'; + } else if (data?.startTime > 0) { + startTime = data.startTime; + document.getElementById('start-button').style.display = 'none'; + document.getElementById('end-button').style.display = 'block'; + } + const startButton = document.getElementById('start-button'); + startButton.onclick = startContest; + const endButton = document.getElementById('end-button'); + endButton.onclick = endContest; + const resetButton = document.getElementById('reset-button'); + resetButton.onclick = resetData; + const messagesButton = document.getElementById('messages-button'); + messagesButton.onclick = switchMessages; + update(); +} + +async function fetchMessages() { + const data = await chrome.storage.local.get('messages'); + if (data.messages && data.messages.length > messagesCount) { + const messagesEl = document.getElementById('messages'); + let html = messagesEl.innerHTML; + for (let i = messagesCount; i < data.messages.length; i++) { + const {displayTime, completionTime, name, text} = data.messages[i]; + html += `
${displayTime}${name}${text}
` + } + messagesEl.innerHTML = html; + messagesCount = data.messages.length; + } +} + +function sendMessage(message, callback) { + chrome.tabs.query({active: true, currentWindow: true}, tabs => { + tabs.forEach(tab => chrome.tabs.sendMessage(tab.id, message, callback)); + }); +} + +function startContest() { + startTime = Date.now(); + endTime = 0; + chrome.storage.local.set({ startTime: startTime, endTime: 0, messages: [] }); + document.getElementById('start-button').style.display = 'none'; + document.getElementById('end-button').style.display = 'block'; + document.getElementById('reset-button').style.display = 'none'; + document.getElementById('messages').innerHTML = ''; + if (showMessages) switchMessages(); +} + +function endContest() { + endTime = Date.now(); + chrome.storage.local.set({ endTime: endTime }); + document.getElementById('start-button').style.display = 'block'; + document.getElementById('end-button').style.display = 'none'; + document.getElementById('reset-button').style.display = 'block'; +} + +function resetData() { + chrome.storage.local.set({ startTime: 0, endTime: 0, messages: [] }); + startTime = 0; + endTime = 0; + document.getElementById('time').innerHTML = '0:00:00'; + document.getElementById('reset-button').style.display = 'none'; + document.getElementById('messages').innerHTML = ''; + if (showMessages) switchMessages(); +} + +function switchMessages() { + const messagesEl = document.getElementById('messages'); + const messagesButtonEl = document.getElementById('messages-button'); + if (showMessages) { + messagesEl.style.display = 'none'; + messagesButtonEl.innerHTML = 'show messages'; + } else { + messagesEl.style.display = 'block'; + messagesButtonEl.innerHTML = 'hide messages'; + } + showMessages = !showMessages; +} + +function dis(n) { + if (n < 10) return "0" + n; + return n.toString(); +} + +function update() { + // Update the display time in the popup. + let displayTime = '0:00:00'; + if (startTime != 0) { + let rawTime = Math.floor((Date.now() - startTime) / 1000); + if (endTime > 0) rawTime = Math.floor((endTime - startTime) / 1000); + displayTime = `${Math.floor(rawTime / 3600)}:${dis(Math.floor((rawTime % 3600) / 60))}:${dis(rawTime % 60)}`; + } + document.getElementById('time').innerHTML = displayTime; + setTimeout(update, 1000); + + // Fetch and save new messages. + fetchMessages(); +} + +setTimeout(init, 20); diff --git a/scripts/content.js b/scripts/content.js new file mode 100644 index 0000000..5c0937f --- /dev/null +++ b/scripts/content.js @@ -0,0 +1,73 @@ +const seenMessages = new Set(); +const seenDataAnnounceMessages = new Set(); +let awaitingTimes = []; +let messages = []; +let startTime = 0; + +async function init() { + const data = await chrome.storage.local.get(['startTime', 'messages']); + if (data?.startTime > 0) { + startTime = data.startTime; + } + if (data.messages) { + messages = data.messages; + } + checkNewMessages(); + update(); +} + +function dis(n) { + if (n < 10) return "0" + n; + return n.toString(); +} + +function displayTime(rawTime) { + if (rawTime < 3600) return `${dis(Math.floor((rawTime % 3600) / 60))}:${dis(rawTime % 60)}`; + return `${Math.floor(rawTime / 3600)}:${dis(Math.floor((rawTime % 3600) / 60))}:${dis(rawTime % 60)}`; +} + +function checkNewMessages() { + // Check announce messages and log their appearing times. + const announceMessageElements = document.querySelectorAll("div[data-announce-message]"); + for (let e of announceMessageElements) { + if (seenDataAnnounceMessages.has(e)) continue; + seenDataAnnounceMessages.add(e); + const completionTime = Math.floor((Date.now() - startTime) / 1000); + awaitingTimes.push(completionTime); + } + + // Check in-call messages. + const messageElements = document.querySelectorAll("div[data-message-id] div[jscontroller]"); + for (let e of messageElements) { + if (seenMessages.has(e)) continue; + seenMessages.add(e); + let completionTime = Math.floor((Date.now() - startTime) / 1000); + + // If awaiting times are available, use them first (i.e. the messages section just got opened). + if (awaitingTimes.length) completionTime = awaitingTimes.shift(); + const messageNode = e.parentNode.parentNode.parentNode.parentNode; + const rootNode = messageNode.parentNode.parentNode; + const name = rootNode.firstChild.firstChild.innerHTML; + const dTime = displayTime(completionTime); + messages.push({completionTime: completionTime, name: name, text: e.innerHTML, displayTime: dTime}); + e.innerHTML = `${dTime}${e.innerHTML}` + } + chrome.storage.local.set({ messages: messages }); + + // If awaiting messages are still present, but the messages section is opened with no new messages, + // delete all awaiting messages (that should never happen though, there was an error somewhere). + if (messageElements.length && awaitingTimes.length) awaitingTimes = []; + + setTimeout(checkNewMessages, 100); +} + +async function update() { + const data = await chrome.storage.local.get(['startTime', 'endTime']); + if (data?.startTime >= 0 && data.startTime != startTime) { + startTime = data.startTime; + messages = []; + } + setTimeout(update, 2000); +} + +setTimeout(init, 20);