New Member
I was asked by my Stake President two years ago to go and certify all the members that are not on the sex offender registries per church guidance. I wrote a little script to do it and got it done. A few months ago we merged our stake with another that hadn't done it yet, so we needed to do another giant bulk run. I made the script more dogged, cleaned it up and posted it here for your use. Enjoy. This should be able to be used for wards as well that want to get it done. It doesn't look at the registries for you. You just do that part manually after adding the certifications to your whole unit. See the instructions in the comment below. Enjoy!
Code:
/*
* UtahCertify.js by Dave Bates
*
* For running by stake presidency. This script certifies everyone in your stake with the certification. After it
* is done go through the sex offender registries in your area and remove their certifications. If you are like our
* Stake there will be under 10.
*
* You must put your church username and password in the constants below. This allows it to retry when the church's
* site has a failure. If the script is interrupted in any way you will hopefully know about how far it got and you
* can resume by changing the START index below to that index in the member list.
*
* To use this script you need to:
* install node
* npm -i puppeteer puppeteer-core
* put this script on your computer somewhere with filename UtahCertify.js
* change the username and password to your church username and password
* then node ./ UtahCertify.js
*
* Enjoy the slowness of the church website. I tried to make this use multiple pages at once, but the website kept
* having trouble.
*/
const username = 'add your username';
const password = 'add your password';
const CERTIFICATION_NAME = 'Utah 2024 Youth Service Organizations';
const START = 0;
const MAX_RETRIES = 10;
const puppeteer = require('puppeteer');
(async () => {
var browser;
var page;
var profilePage;
var retries = 0;
var index = START;
async function openAndLogin() {
if (browser) {
try {
await browser.close();
} catch {
}
}
browser = await puppeteer.launch({
headless:false,
defaultViewport:null
});
page = (await browser.pages())[0];
profilePage = null;
await page.goto('https://lcr.churchofjesuschrist.org/mlt/records/member-list?lang=eng');
console.log("Logging in...");
await page.waitForSelector('input[data-form-type="username"]');
await page.click('input[data-form-type="username"]', {
clickCount:3
});
await page.type(
'input[data-form-type="username"]',
username
);
await page.click('#button-primary');
await page.waitForSelector('input[data-form-type="password"]');
await page.click('input[data-form-type="password"]', {
clickCount:3
});
await page.type(
'input[data-form-type="password"]',
password
);
await page.click('#button-primary');
await page.waitForFunction(() => {
const sel = document.querySelector('select.eden-form-part-input__control');
return sel && sel.options.length > 1;
}, {timeout:0});
console.log("Login complete.");
retries++;
}
await openAndLogin();
await page.select(
'select.eden-form-part-input__control:has(option[value="ALL"])',
'ALL'
);
await page.waitForFunction(() => {
return document.querySelectorAll(
'button[data-member-card-person-uuid]'
).length > 0;
}, {timeout:0});
// Collect UUIDs
const uuids = await page.$$eval(
'button[data-member-card-person-uuid]',
buttons => buttons.map(b => b.dataset.memberCardPersonUuid)
);
console.log(`Found ${uuids.length} members.`);
for (; index < uuids.length; index++) {
const uuid = uuids[index];
do {
if (retries > MAX_RETRIES)
{
console.log("Giving up after " + MAX_RETRIES + " retries for " + index);
retries = 0;
break;
}
console.log(`(${index + 1}/${uuids.length}) ${uuid}`);
try {
if (profilePage === null)
{
profilePage = await browser.newPage();
}
await profilePage.goto(
`https://lcr.churchofjesuschrist.org/mlt/records/member-profile/${uuid}?lang=eng`,
{
waitUntil:'networkidle2'
}
);
// Open Certifications tab
const which = await Promise.race([
profilePage.waitForSelector('#tab-certification').then(() => 'profile'),
profilePage.waitForSelector('input[data-form-type="password"]').then(() => 'login'),
new Promise(resolve =>
setTimeout(() => resolve('timeout'), 20000)
)
]);
if (which === 'login') {
console.log("Need to log in again.");
await openAndLogin();
continue;
}
await profilePage.evaluate(() => {
document.querySelector('#tab-certification').click();
});
// Wait for certification section
await profilePage.waitForSelector('#certification');
await profilePage.waitForNetworkIdle();
// Does this certification already exist?
const hasCertification = await profilePage.evaluate((certName) => {
return [...document.querySelectorAll('#certification td.eden-table-td')]
.some(td => td.textContent.includes(certName));
}, CERTIFICATION_NAME);
if (hasCertification) {
console.log(' Already has certification.');
retries = 0;
break;
}
// Click Add Certification
const addButton = await profilePage.waitForSelector(
'.add-certification',
{timeout:15000}
).catch(() => null);
if (!addButton) {
console.log(" Member cannot receive certifications. Skipping.");
retries = 0;
break;
}
console.log(' Adding certification...');
await addButton.evaluate(button => button.click());
// Wait for first text input
const input = await profilePage.waitForSelector('input[type="text"]');
await input.click({clickCount:3});
await input.type(CERTIFICATION_NAME);
// Click add button
await profilePage.evaluate(() => {
const addButton = [...document.querySelectorAll('button')]
.find(button => button.textContent.trim() === 'Add');
if (!addButton) {
throw new Error('Add button not found.');
}
addButton.click();
});
// Wait until the certification appears in the table
await profilePage.waitForFunction(
(certName) => {
return [...document.querySelectorAll('#certification td.eden-table-td')]
.some(td => td.textContent.includes(certName));
},
{
timeout:15000
},
CERTIFICATION_NAME
);
console.log(' Saved.');
retries = 0;
break;
} catch (err) {
console.error(` FAILED for ${uuid}`);
console.error(err);
// retry with new browser
await openAndLogin();
}
} while (true);
}
await profilePage.close();
console.log('Finished processing all members.');
})();