|
(async () => { |
|
await autoMuteCamera(); |
|
// await autoMuteMic(); |
|
await autoJoin(); |
|
remindToStartRecording(/録画|design sync-?up|輪読会|ブリーフィング|briefing/i); |
|
stayJoined(); |
|
})(); |
|
|
|
//=== Util |
|
//============================================================================================== |
|
async function mut(fn) { |
|
return new Promise((resolve) => { |
|
const observer = new MutationObserver(() => { |
|
const result = fn(); |
|
if (typeof result === "undefined") return; |
|
observer.disconnect(); |
|
resolve(result); |
|
}); |
|
observer.observe(document.body, { |
|
childList: true, |
|
subtree: true, |
|
}); |
|
}); |
|
} |
|
|
|
async function findAllNodesAsync(selector, filterFn) { |
|
const find = () => { |
|
const nodes = [...document.body.querySelectorAll(selector)]; |
|
if (nodes.length === 0) return; |
|
if (filterFn) { |
|
const filtered = nodes.filter(filterFn); |
|
if (filtered.length === 0) return; |
|
return filtered; |
|
} |
|
return nodes; |
|
}; |
|
return find() ?? mut(find); |
|
} |
|
|
|
async function findNodeAsync(selector, filterFn) { |
|
const nodes = await findAllNodesAsync(selector, filterFn); |
|
return nodes[0]; |
|
} |
|
|
|
async function getMeetingTitle() { |
|
const id = window.location.pathname.substr(1); |
|
|
|
const getTitle = () => { |
|
const m = document.title.match(/^Meet - (.+)$/); |
|
const titleOrId = m?.[1]; |
|
if (!titleOrId || titleOrId === id) return; |
|
return titleOrId; |
|
}; |
|
|
|
return new Promise((resolve) => { |
|
const tick = () => { |
|
const title = getTitle(); |
|
if (title) return resolve(title); |
|
setTimeout(tick, 50); |
|
}; |
|
tick(); |
|
}); |
|
} |
|
|
|
//=== Actions |
|
//============================================================================================== |
|
async function autoMuteCamera() { |
|
const btn = await findNodeAsync(`div[role="button"][aria-label*="camera"][data-is-muted]`); |
|
if (btn.dataset.isMuted !== "false") return; |
|
|
|
btn.click(); |
|
console.log("[autoMuteCamera] activated"); |
|
} |
|
|
|
async function autoMuteMic() { |
|
const btn = await findNodeAsync(`div[role="button"][aria-label*="microphone"][data-is-muted]`); |
|
if (btn.dataset.isMuted !== "false") return; |
|
|
|
btn.click(); |
|
console.log("[autoMuteMic] activated"); |
|
} |
|
|
|
async function autoJoin() { |
|
await getMeetingTitle(); |
|
await mut(() => { |
|
const el = document.querySelector(`[role="heading"][aria-level="1"]`); |
|
if (!el) return; |
|
const title = el.innerText; |
|
if (!title || title.includes("Ready to join?")) return; |
|
return true; |
|
}); |
|
|
|
const joinButton = await findNodeAsync(`button`, (node) => node.innerText.includes("Join now")); |
|
joinButton.click(); |
|
console.log("[autoJoin] activated"); |
|
} |
|
|
|
async function remindToStartRecording(meetingTitlePattern) { |
|
const title = await getMeetingTitle(); |
|
if (!meetingTitlePattern.test(title)) return; |
|
|
|
const activitiesButton = await findNodeAsync(`button[aria-label="Activities"]`); |
|
activitiesButton.click(); |
|
console.log("[remindToStartRecording] activated"); |
|
} |
|
|
|
async function stayJoined() { |
|
const dialog = await findNodeAsync(`div[aria-modal][role="dialog"][aria-label="Are you still there?"]`); |
|
const btn = dialog.querySelector(`button[data-mdc-dialog-button-default]`); // "Stay in the call" |
|
btn.click(); |
|
|
|
console.log("[stayJoined] activated"); |
|
setTimeout(stayJoined, 1 * 60 * 1000); // Rerun every minutes |
|
} |