|
((maxStrLength = 2000) => { |
|
try { |
|
/* 1. Find the objects corresponding to the participants by recursively |
|
walking the state of the webapp. */ |
|
function findParticipants(current, depth = 0) { |
|
if (depth > 7) |
|
return; /* We can find it at a lower depth. */ |
|
if (typeof current !== "object" || current === null || current === window) |
|
return; /* We're only interested in objects. */ |
|
|
|
/* Iterate over descriptors to make sure we don't access computed properties. */ |
|
const descriptors = Object.getOwnPropertyDescriptors(current); |
|
|
|
for (const prop in descriptors) { |
|
if (prop.startsWith('["spaces/')) |
|
/* We're looking for the object whose keys start with '["spaces/'. */ |
|
return Object.values(current); |
|
|
|
const item = findParticipants(descriptors[prop].value, depth + 1); |
|
|
|
if (item !== undefined) |
|
return item; |
|
} |
|
} |
|
|
|
const rootState = Object.entries(window).find(x => x[0].startsWith("closure_lm_"))[1], |
|
participants = findParticipants(rootState), |
|
names = []; |
|
|
|
/* 2. For each object, determine the participant's name. */ |
|
function findName(obj) { |
|
/* The property names are non-deterministic and may change at any time, but the |
|
participant's name is always at 'participant.Aa[1]', where 'Aa' can be an |
|
arbitrary string. |
|
Walk the object and hope that the first match corresponds to the string. */ |
|
for (const prop in obj) { |
|
const value = obj[prop]; |
|
|
|
if (typeof value === "object" && value !== null && typeof value[1] === "string") |
|
return value[1]; |
|
} |
|
} |
|
|
|
for (let i = 0; i < participants.length; i++) { |
|
const name = findName(participants[i]); |
|
|
|
if (names.indexOf(name) === -1) |
|
/* 3. Remove duplicates. */ |
|
names.push(name); |
|
} |
|
|
|
if (names.length === 0) |
|
throw new Error("Could not find any name."); |
|
|
|
/* 4. Sort the names lexicographically. */ |
|
names.sort((a, b) => a.localeCompare(b)); |
|
|
|
/* 5. Chrome limits to 2K characters the number of characters in the string given |
|
to "prompt", so we split the output in several groups if needed. */ |
|
const groups = names.slice(1).reduce((groups, name) => { |
|
if (groups[0].length + name.length + 1 >= maxStrLength) { |
|
groups.unshift(name); |
|
} else { |
|
groups[0] += ", " + name; |
|
} |
|
|
|
return groups; |
|
}, [names[0]]).reverse(); |
|
|
|
/* 6. Finally, output all groups using "prompt". */ |
|
for (let i = 0, {length} = groups; i < length; i++) { |
|
const suffix = length === 1 ? "" : " (" + (i + 1) + "/" + length + ")", |
|
message = "Please find the list of participants below" + suffix + "."; |
|
|
|
if (prompt(message, groups[i]) === null) |
|
break; /* User pressed "Cancel", so we stop the loop. */ |
|
} |
|
} catch (e) { |
|
alert("Unexpected error when running the script: " + e); |
|
} |
|
})() |