A script for adding Utah Youth Service Organizations Certifications

Do you have a useful link that involves the Church and the technology discussed on this site? Post your links and resources here.
datbates
New Member
Posts: 3
Joined: Tue Jan 17, 2017 7:02 am

A script for adding Utah Youth Service Organizations Certifications

Post by datbates »

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: Select all

/*
 * 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.');
})();
rmrichesjr
Community Moderators
Posts: 4730
Joined: Thu Jan 25, 2007 11:32 am
Location: Dundee, Oregon, USA

Re: A script for adding Utah Youth Service Organizations Certifications

Post by rmrichesjr »

There was a lively discussion among moderators and the community administrator regarding the above post that contains the script. The following is my interpretation of the issues raised during that discussion.

- Initially, there was concern that the script scraped data from LCR. However, further study indicated it does not pull data from LCR but merely posts data to LCR more quickly than a clerk could do manually.

- None of those involved in the moderator discussion have direct access to the instructions from Church Headquarters or the Utah Area Office(s) regarding this certification task. From what we gather, the purpose is to comply with this Utah law: https://le.utah.gov/~2024/bills/static/SB0158.html

- In some stakes the stake clerk is doing the work for the entire stake, which takes a terrible amount of time. We understand that in other stakes the ward clerk or ward organizations are doing the work for each ward--and then the stake does spot checks to audit the work the ward people had done.

- We understand some stakes are using Claude or other AI tools. We are concerned that would violate Handbook instruction in section 38.8.48.3

- When a clerk adds his credentials to the script, the script will need to be protected to avoid leaking the clerk's credentials. Adding the credentials to a copy of the script and then deleting that copy after running it would seem to be a reasonable precaution.

- This script mentions two different certifications but appears to only do one.

- With a pre-packaged script like this, it is conceivable that someone might use the script to register the certifications and then neglect to do the manual checking of the registries to remove certifications for any members found to be in the registries.

- This script certifies everyone in the ward, apparently including small children. Estimates by moderators are that only 20-50 adults need to be certified to satisfy the requirements of the law. However, we don't know the wording of the Church instructions, so maybe certifying everyone is required by the Church.

- By certifying everyone, there is no built-in reminder to re-check members as time advances. Pre-clearing members (perhaps years in advance of a relevant calling) could introduce the following problematic hypothetical sequence:

1. A member who currently has no calling related to youth or children could be certified by this script.

2. Then, perhaps some years later, the member could appear on a registry.

3. Then, the member could receive a calling that interacts with youth or children.

That could result in an erroneous certification that could have been avoided if the check had been done and the certification created at the time the calling was extended. That would be legally problematic for the Church.

- The server load to create certifications in a rapid-fire way could cause problems for Church infrastructure. Some time ago, a new automated search tool came out that caused problems for FamilySearch.
russellhltn
Community Administrator
Posts: 36746
Joined: Sat Jan 20, 2007 2:53 pm
Location: U.S.

Re: A script for adding Utah Youth Service Organizations Certifications

Post by russellhltn »

rmrichesjr wrote: Thu Jul 09, 2026 4:59 pm Adding the credentials to a copy of the script and then deleting that copy after running it would seem to be a reasonable precaution.
Just make sure to remove it from the recycle bin!
Have you searched the Help Center? Try doing a Google search and adding "site:churchofjesuschrist.org/help" to the search criteria.

So we can better help you, please edit your Profile to include your general location.
rmrichesjr
Community Moderators
Posts: 4730
Joined: Thu Jan 25, 2007 11:32 am
Location: Dundee, Oregon, USA

Re: A script for adding Utah Youth Service Organizations Certifications

Post by rmrichesjr »

russellhltn wrote: Thu Jul 09, 2026 5:30 pm Just make sure to remove it from the recycle bin!
Good point for those who use systems where delete doesn't really mean delete.
datbates
New Member
Posts: 3
Joined: Tue Jan 17, 2017 7:02 am

Re: A script for adding Utah Youth Service Organizations Certifications

Post by datbates »

Thanks for allowing this post moderators. Sorry to have caused you so much trouble!

The first time I did this, I didn't teach the script how to log in and did it manually. This time when I tried it the website kept kicking me out and requiring me to login again, so I finally ended up automating it. Thanks and good luck!

Return to “Links & Resources”