Skip to content

ChurchofJesusChristDev/ministering-assignments

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Send a friend email for all assignments with plain-text contact information

Most people don't know where to check their ministering assignment, and don't know when it has changed.

We want to periodically send a church email (and eventually texts) showing assignments in a simple way, and with a call to action.

https://lcr.churchofjesuschrist.org/ministering?lang=eng&type=EQ&tab=assigned

Overview

Here's the step-by-step overview:

  1. "Scrape" the ministering assignment data from the JSON "script" tag in the HTML
  2. Transform that data from the format used for page layout to a more generally useful format
  3. Fetch the missing person information (phone numbers, email, address) from the card API
  4. Template the complete data set as you wish to display it in print or email
  5. Send the batch of emails, one API call per each

Demo

  1. Visit Ministering Assignments at https://lcr.churchofjesuschrist.org/ministering?lang=eng&type=EQ&tab=assigned.
  2. Cmd ⌘ + Alt ⌥ + i to open the JavaScript inspector console
  3. Copy and paste this script
    var script = document.createElement('script');
    script.src="https://churchofjesuschristdev.github.io/ministering-assignments/demo.js";
    document.body.append(script);
  4. Wait for it to load.
  5. JSON: You can view or Download the assignments as JSON:
    console.log(CJCD.toJSON());
    CJCD.download('ministering.json', null, 2);
  6. CSV: You can view or Download as CSV as well:
    console.log(CJCD.toCSV());
    CJCD.download('assignments.csv', null, 2);

Security Notice: Generally speaking it's a bad idea to run scripts from randos on the Internet. However, you can view the full contents of that script in this repository, and being that it's encrypted with HTTPS, and hosted via Github, you have every reasonable assurance that the script is authentically from this repository. :)

How to Send an Email

This part is relatively simple.

Authentication is cookie-based, so no authentication details are provided.

async function sendEmail(message) {
  return fetch("https://lcr.churchofjesuschrist.org/services/leader-messaging/send-message?lang=eng", {
    "headers": {
      "accept": "application/json, text/plain, */*",
      "content-type": "application/json;charset=UTF-8"
    },
    "credentials": "include",
    "method": "POST",
    "body": JSON.stringify(message, null, 2)
  });
}
var message = {
  lang: 'eng',
  recipients: [ 1000000001 ],
  allowReplyAll: false,
  subject: "[Test] You're a Wizard Harry!",
  messageBody: 'Harry! You've been accepted to Hogwarts School Stake.",
  type: 'EQ'
};

sendEmail(message);

Getting the Data from the HTML

Unfortunately, it doesn't seem possible to get the JSON you need from the API.

Instead, you have to get it from a JSON object stored in a quasi "script tag".

var data = JSON.parse($('script[type="application/json"]').innerText);

The format of the data in the HTML isn't ideal, but it's workable. Here's an abreviated view:

{
  "props": {
    "pageProps": {
      "initialState": {
        "ministeringData": {

	  //
	  // misnomer: these are districts
	  //
          "elders": [
            {
              "districtName": "District 1",
              "districtUuid": "50000000-0000-4000-8000-000000000001",
     	      "supervisorName": "Black, Sirius",
              "supervisorLegacyCmisId": 1000000005,
              "supervisorPersonUuid": "10000000-0000-4000-8000-000000000005",

	      "companionships": [
	        "id": "60000000-0000-4000-8000-000000000001",

		"ministers": [
                  {
                    "personUuid": "10000000-0000-4000-8000-000000000003",
                    "legacyCmisId": 1000000003,
                    "name": "Weasley, Ronald",
                    "nameOrder": 2,
                    "email": "ron.b.weasley@example.com",
                    "interviews": [
                      {
                        "id": "A0000000-0000-4000-8000-000000000001",
                        "date": "2020-03-01T00:00:00.000",
                        "timestamp": "2020-03-01T06:00:00.000+0000"
                      }
		    ],
                    "youthBasedOnAge": false
		  }
		],

		"assignments": [
                  {
		    //
		    // misnomer: this refers to head of household, as the family identifier
		    //
                    "personUuid": "10000000-0000-4000-8000-000000000001",
                    "legacyCmisId": 1000000001,
                    "name": "Potter, Harry James & Ginevra Molly",
                    "nameOrder": 1,
                    "youthBasedOnAge": false
                  }
		],

                "recentlyChangedDate": "2021-06-01T06:00:00.000+0000",
                "recentlyChangedDateInMilliseconds": 1622527200000
	      ]
            }
          ]
        }
      }
    }
  }
}

Take a look at the Full Data Shape for more detail.

And this is how you can access it

// Misnomer, these are Ministering Districts
data.props.pageProps.initialState.ministeringData.elders

// Sadly, no ID
data.props.pageProps.initialState.ministeringData.elders[0].supervisorName

// Individual ID and email, but no phone number
data.props.pageProps.initialState.ministeringData.elders[0].companionships[0].ministers[0].legacyCmisId
// Individual ID, but no contact info
data.props.pageProps.initialState.ministeringData.elders[0].companionships[0].assignments[0].legacyCmisId

If we want to omit the most garbage and get the most useful data only:

console.log(
  JSON.stringify(
    {
      props: {
        pageProps: {
          initialState: {
            ministeringData: {
              elders: data.props.pageProps.initialState.ministeringData.elders
            }
          }
        }
      }
    },
    null,
    2
  )
);

You can then save that to a file and use it as a cache.

Get Complete Ministering Assignment Data

// load companionship data and card cache, if any
CJCD.init(data, cards || {});

// transform the data into an individual-oriented format
CJCD.organize();

// get the missing information from card API
await CJCD.getCards();

var assignments = CJCD.toJSON();

assignments will look like this:

[
  {
    "member": {
      "nickname": "",
      "given_name": "",
      "family_name": "",
      "phone": "",
      "email": "",
      "address": "",
      "district": ""
    },
    "companions": [],
    "assignments": [],
    "ministers": []
  }
]

If you want to get the card values to save them for local caching:

console.log(JSON.stringify(CJCD._cards, null, 2));

This will put all person info in cards and all companionship info in people.

var CJCD;

CJCD = (function () {
    var ids = {};
    var people = {};

    function getPerson(id) {
        if (!people[id]) {
            ids[id] = true;
            people[id] = {
                companions: {},
                assignments: {},
                ministers: {},
            };
        }
        return people[id];
    }

    function organize() {
        CJCD._data.props.pageProps.initialState.ministeringData.elders.forEach(
            function (district) {
                district.companionships.forEach(function (ship, i) {
                    //console.log("companionship", i);
                    // this is a little imperfect for people in multiple companionships, but should work generally

                    ship.ministers.forEach(function (m, j) {
                        //console.log("minister", j);
                        ids[m.legacyCmisId] = true;

                        var p = getPerson(m.legacyCmisId);
                        if (ship.ministers) {
                            ship.ministers.forEach(function (n) {
                                if (m === n) {
                                    return;
                                }
                                p.companions[n.legacyCmisId] = true;
                            });
                        }

                        if (ship.assignments) {
                            ship.assignments.forEach(function (a) {
                                var q = getPerson(a.legacyCmisId);
                                q.ministers[m.legacyCmisId] = true;

                                p.assignments[a.legacyCmisId] = true;
                                ids[a.legacyCmisId] = true;
                            });
                        }
                    });
                });
            }
        );
    }

    async function getCards() {
        for (id in ids) {
            await getCard(id);
        }
    }

    function getCachedCard(id) {
        return CJCD._cards[id];
    }

    async function getCard(id) {
        if (CJCD._cards[id]) {
            return CJCD._cards[id];
        }
        //console.log(Object.keys(cards).length, id);

        var cardUrl =
            `https://lcr.churchofjesuschrist.org/services/member-card` +
            `?id=${id}&includePriesthood=true&lang=eng&type=INDIVIDUAL`;

        return fetch(cardUrl, {
            credentials: "same-origin",
        }).then(function (resp) {
            return resp.json().then(function (data) {
                CJCD._cards[id] = data;
                return CJCD._cards[id];
            });
        });
    }

    function toJSON() {
        var assignments = [];
        return Object.keys(CJCD.ids).map(function (id) {
            var p = CJCD.getPerson(id);
            var c = CJCD.getCachedCard(id);
            var isMinister = false;

            var assignment = {
                member: p, // TODO format member
                assignments: [],
                ministers: [],
                companions: [],
            };

            if (Object.keys(p.assignments).length) {
                isMinister = true;
                assignment.assignments = Object.keys(p.assignments).map(
                    function (m) {
                        var family = CJCD.getCachedCard(m);
                        // TODO format in a reasonable way
                        return family;
                    }
                );
            }

            if (Object.keys(p.companions).length > 0) {
                isMinister = true;
                assignment.companions = Object.keys(p.companions).map(function (
                    id
                ) {
                    var companion = CJCD.getCachedCard(id);
                    // TODO format in a reasonable way
                    return companion;
                });
            }

            if (Object.keys(p.ministers).length > 0) {
                assignment.ministers = Object.keys(p.ministers).map(function (
                    id
                ) {
                    var minister = CJCD.getCachedCard(id);
                    // TODO format in a reasonable way
                    return minister;
                });
            }

            return assignment;
        });
    }

    return {
        _data: null,
        _cards: null,
        _people: people,
        ids: ids,
        init: async function (_data, _cards) {
            CJCD._data = _data;
            CJCD._cards = _cards || CJCD._cards || {};
            CJCD.organize();
            await CJCD.getCards();
        },
        organize: organize,
        getPerson: getPerson,
        getCard: getCard,
        getCards: getCards,
        getCachedCard: getCachedCard,
        toJSON: toJSON,
    };
})();

if ("undefined" != typeof module) {
    module.exports = CJCD;
}

Templating Emails

This will create an email for everyone who has a ministering assignment and/or is assigned to a set of ministers.

var CJCD;

if ("undefined" === typeof CJCD) {
    CJCD = require("./email.js");
}

var assigneeSubject = "Will you do the Elder's Quorum a Favor?";
var assigneeMessage = `\n<p>We wanted to let know who your ministers are. Feel free to reach out to them if you ever need to borrow a cup of sugar, or get a ride to the airport. :)\n`;

var ministerSubject = "Updated Assignments: Will you do the Elder's Quorum a Favor?";
var ministerMessage =
    `\n<p>We've updated ministering assignments wanted to make sure you have yours. We also have two favors to ask: ` +
    `\n<p>1. We'd like to ask you to pray for the families you minister to by name today (and often). ` +
    `\n<p>2. If you haven't heard from your ministers will you do a little reverse ministering and reach out to them? `;

function formatTel(t) {
    t = (t || "").replace(/\D/g, "").trim();

    if (!t) {
        return "";
    }

    if (t.length > 10) {
        if ("1" !== t[0] || t.length > 11) {
            console.warn("phone too long:", t);
        }
        t = t.slice(-10);
        //return "";
    }

    if (t.length < 10) {
        console.warn("phone too short:", t);
        t = t.padStart(10, "_");
        //return "";
    }

    return `(${t.slice(0, 3)}) ${t.slice(3, 6)}-${t.slice(6, 10)}`;
}

function formatEmail(name, email) {
    if (!email || !/@/.test(email)) {
        return "";
    }
    //return `<pre>"${name}" &lt;${email}&gt;,</pre>`;
    return `${email}`;
}

function formatCard(c) {
    return (
        "<li>" +
        c.spokenName +
        " " +
        c.age +
        " " +
        ("FEMALE" === c.gender ? "F" : "M") +
        "\t" +
        formatTel(c.individualPhone || c.phone) +
        "\t" +
        formatEmail(c.spokenName, c.email) +
        "</li>\n"
    );
}

function helloMinisters(p, c) {
    var lead = `Hi ${c.spokenName.split(" ").shift()}, \n` + assigneeMessage;
    return lead;
}

function helloAssignments(p, c) {
    var lead = `Hey ${c.spokenName.split(" ").shift()}, \n` + ministerMessage;
    return lead;
}

async function createMessages() {
    CJCD.organize();
    await CJCD.getCards();
    return Object.keys(CJCD.ids)
        .filter(function (id) {
            var p = CJCD.getPerson(id);
            var c = CJCD.getCachedCard(id);

            if (!Object.keys(p.assignments)) {
                console.warn("no assignments", c.spokenName, `(${id})`);
                return;
            }

            if (!c.email) {
                console.warn("no email for", c.spokenName, `(${id})`);
                return;
            }

            if (0 === Object.keys(p.ministers).length) {
                console.warn("no ministers for", c.spokenName, `(${id})`);
                // pass
            }

            if (0 === Object.keys(p.companions).length) {
                console.warn("no companions for", c.spokenName, `(${id})`);
                // pass
            }

            if (0 === Object.keys(p.assignments).length) {
                console.warn("no assignment for", c.spokenName, `(${id})`);
                // pass
            }

            return id;
        })
        .map(function (id) {
            var msgs = ['']; // start with a new paragraph

            var p = CJCD.getPerson(id);
            var c = CJCD.getCachedCard(id);
	    var isMinister = false;

            if (Object.keys(p.assignments).length) {
                isMinister = true;
                let msg = `Who you minister to:<br><ul>\n`;
                Object.keys(p.assignments).forEach(function (m) {
                    var assignment = cards[m];
                    msg += formatCard(assignment);
                });
                msg += "</ul>";
                msgs.push(msg);
            }

            if (Object.keys(p.companions).length > 0) {
                isMinister = true;
                let msg = "";
                if (1 === Object.keys(p.companions).length > 1) {
                    msg += `Your companion:<br><ul>\n`;
                } else {
                    msg += `Your companions:<br><ul>\n`;
                }
                Object.keys(p.companions).forEach(function (id) {
                    var companion = cards[id];
                    msg += formatCard(companion);
                });
                msg += "</ul>";
                msgs.push(msg);
            }

            if (Object.keys(p.ministers).length > 0) {
                let msg = `Your ministers:<br><ul>\n`;
                Object.keys(p.ministers).forEach(function (id) {
                    var minister = cards[id];
                    msg += formatCard(minister);
                });
                msg += "</ul>";
                msgs.push(msg);
            }

            if (!msgs.length) {
                console.error("[SANITY] Impossible!", id, "does not exist!");
                return;
            }

            // recipients: [ id ]

            var lead;
            if (
                "MALE" !== c.gender ||
                (0 === Object.keys(p.assignments).length &&
                    0 === Object.keys(p.companions).length)
            ) {
                lead = helloMinisters(p, c);
            } else {
                lead = helloAssignments(p, c);
            }

            var body = lead + msgs.join("\n<p>") + "\n\n";

            var data = {
                lang: "eng",
                allowReplyAll: false,
                recipients: [parseInt(id, 10)],
                subject: isMinister && ministerSubject || assigneeSubject,
                messageBody: body,
                type: "EQ",
            };

            console.log({
                body: JSON.stringify(data),
            });

            return data;
        });
}

var messages = createMessages();

Shape of Data

The JSON is over 1MB, so to examine it quickly I used Matt's JSON-to-Go, and I cut out translations, as that accounted for about 75% of the entire structure.

Again, the purpose is NOT to provide the Go representation of the JSON, but rather that was the simplest tool I know of to convert a huge JSON dataset into a small overview.

type AutoGenerated struct {
	Props struct {
		PageProps struct {
			IsServer bool `json:"isServer"`
			Store    struct {
			} `json:"store"`
			InitialState struct {
				APIStatus struct {
					ShowGenericError bool `json:"showGenericError"`
					IsPageError      bool `json:"isPageError"`
					IsFileNotFound   bool `json:"isFileNotFound"`
					IsCaPage         bool `json:"isCaPage"`
				} `json:"apiStatus"`
				Lang    string `json:"lang"`
				Version string `json:"version"`
				Proxy   struct {
					ProxyOptions       []interface{} `json:"proxyOptions"`
					ProxySearchResults []interface{} `json:"proxySearchResults"`
					Loading            bool          `json:"loading"`
				} `json:"proxy"`
				UserContext struct {
					Environment string `json:"environment"`
					Version     string `json:"version"`
					ContextPath string `json:"contextPath"`
					Unit        struct {
						UnitName            string      `json:"unitName"`
						UnitNumber          int         `json:"unitNumber"`
						Type                string      `json:"type"`
						TranslatedType      string      `json:"translatedType"`
						Children            interface{} `json:"children"`
						ParentUnitName      string      `json:"parentUnitName"`
						ParentUnitNumber    int         `json:"parentUnitNumber"`
						ParentType          string      `json:"parentType"`
						GrandparentType     string      `json:"grandparentType"`
						MissionName         string      `json:"missionName"`
						MissionUnitNumber   int         `json:"missionUnitNumber"`
						AreaName            string      `json:"areaName"`
						AreaUnitNumber      int         `json:"areaUnitNumber"`
						MsrOfficeName       string      `json:"msrOfficeName"`
						MsrOfficeUnitNumber int         `json:"msrOfficeUnitNumber"`
					} `json:"unit"`
					IsLcrClassCallingHtvtEnabled bool `json:"isLcrClassCallingHtvtEnabled"`
					SubOrgs                      []struct {
						UnitNumber          int           `json:"unitNumber"`
						SubOrgID            int           `json:"subOrgId"`
						OrgTypeIds          []int         `json:"orgTypeIds"`
						DefaultOrgTypeIds   []int         `json:"defaultOrgTypeIds"`
						Name                string        `json:"name"`
						Children            []interface{} `json:"children"`
						UserCanEditCallings bool          `json:"userCanEditCallings"`
						IsClass             bool          `json:"isClass"`
						IsRealClass         bool          `json:"isRealClass"`
						IsSplit             bool          `json:"isSplit"`
						ClassGroup          interface{}   `json:"classGroup"`
						ParentName          interface{}   `json:"parentName"`
						FirstTypeID         int           `json:"firstTypeId"`
						Gender              interface{}   `json:"gender"`
						IsCombined          bool          `json:"isCombined"`
					} `json:"subOrgs"`
					UnitOrgs []struct {
						Children       interface{} `json:"children"`
						UnitOrgUUID    string      `json:"unitOrgUuid"`
						UnitUUID       string      `json:"unitUuid"`
						UnitNumber     int         `json:"unitNumber"`
						UnitOrgName    string      `json:"unitOrgName"`
						UnitOrgTypeIds []int       `json:"unitOrgTypeIds"`
						IsClass        bool        `json:"isClass"`
					} `json:"unitOrgs"`
					FullDateFormatJs         string `json:"fullDateFormatJs"`
					FullDateFormatOmitYearJs string `json:"fullDateFormatOmitYearJs"`
					EmptyNameGroup           struct {
						FormattedLocal interface{} `json:"formattedLocal"`
						FormattedLatin interface{} `json:"formattedLatin"`
						Name1          struct {
							Family         interface{} `json:"family"`
							Given          interface{} `json:"given"`
							Suffix         interface{} `json:"suffix"`
							TranslitSource bool        `json:"translitSource"`
							WritingSystem  string      `json:"writingSystem"`
							Label          string      `json:"label"`
							LabelKey       string      `json:"labelKey"`
						} `json:"name1"`
						Name2        interface{} `json:"name2"`
						Name3        interface{} `json:"name3"`
						AutoRomanize bool        `json:"autoRomanize"`
					} `json:"emptyNameGroup"`
					LdsAccountUsername       string   `json:"ldsAccountUsername"`
					LdsAccountPreferredName  string   `json:"ldsAccountPreferredName"`
					IndividualID             int64    `json:"individualId"`
					Roles                    []string `json:"roles"`
					ActivePosition           int      `json:"activePosition"`
					ActivePositionUnitNumber int      `json:"activePositionUnitNumber"`
					ActivePositionEnglish    string   `json:"activePositionEnglish"`
					Positions                []struct {
						PositionTypeID int    `json:"positionTypeId"`
						PositionName   string `json:"positionName"`
						UnitNumber     int    `json:"unitNumber"`
					} `json:"positions"`
					Proxied                       bool   `json:"proxied"`
					BetaEnv                       bool   `json:"betaEnv"`
					BetaTerms                     bool   `json:"betaTerms"`
					NonBetaURL                    string `json:"nonBetaUrl"`
					BetaURL                       string `json:"betaUrl"`
					AddressURL                    string `json:"addressUrl"`
					CdolURL                       string `json:"cdolUrl"`
					CdolUnitURL                   string `json:"cdolUnitUrl"`
					LuHelpURL                     string `json:"luHelpUrl"`
					ChangeBoundaryURL             string `json:"changeBoundaryUrl"`
					ChangeBoundaryStakeURL        string `json:"changeBoundaryStakeUrl"`
					ViewBoundaryProposalStatusURL string `json:"viewBoundaryProposalStatusUrl"`
					StakeConferencesURL           string `json:"stakeConferencesUrl"`
					SurveyURL                     string `json:"surveyUrl"`
					NewBishopURL                  string `json:"newBishopUrl"`
					NewBishopStatusURL            string `json:"newBishopStatusUrl"`
					NewStakeCounselorURL          string `json:"newStakeCounselorUrl"`
					NewPatriarchURL               string `json:"newPatriarchUrl"`
					RecommendMissionPresidentURL  string `json:"recommendMissionPresidentUrl"`
					WelfareServicesURL            string `json:"welfareServicesUrl"`
					CqaBulkWait                   string `json:"cqaBulkWait"`
					Teasers                       struct {
						SmallTeasers  []interface{} `json:"smallTeasers"`
						MediumTeasers [][]struct {
							ID             string      `json:"id"`
							Feature        interface{} `json:"feature"`
							URL            interface{} `json:"url"`
							URLInNewWindow bool        `json:"urlInNewWindow"`
							ImageURL       string      `json:"imageUrl"`
							Title          string      `json:"title"`
							Description    string      `json:"description"`
						} `json:"mediumTeasers"`
					} `json:"teasers"`
					Menus []struct {
						Type                 string      `json:"type"`
						HiddenMenuSearchOnly bool        `json:"hiddenMenuSearchOnly"`
						Name                 string      `json:"name"`
						LiClass              interface{} `json:"liClass"`
						UlClass              interface{} `json:"ulClass"`
						SubMenuID            interface{} `json:"subMenuId"`
						AllowEmptyMenu       bool        `json:"allowEmptyMenu"`
						Sorted               bool        `json:"sorted"`
						IncludeRoles         [][]string  `json:"includeRoles"`
						ExcludeRoles         interface{} `json:"excludeRoles"`
						Item                 struct {
							HiddenMenuSearchOnly bool        `json:"hiddenMenuSearchOnly"`
							Name                 interface{} `json:"name"`
							MenuSearchName       interface{} `json:"menuSearchName"`
							Title                interface{} `json:"title"`
							ParentTitle          interface{} `json:"parentTitle"`
							URL                  string      `json:"url"`
							URLInNewWindow       bool        `json:"urlInNewWindow"`
							UISref               interface{} `json:"uiSref"`
							UISrefOpts           interface{} `json:"uiSrefOpts"`
							LiClass              interface{} `json:"liClass"`
							IncludeRoles         interface{} `json:"includeRoles"`
							ExcludeRoles         interface{} `json:"excludeRoles"`
							React                bool        `json:"react"`
							SubOrgID             interface{} `json:"subOrgId"`
						} `json:"item"`
						Items            interface{} `json:"items"`
						Columns          interface{} `json:"columns"`
						ParentSubOrgType interface{} `json:"parentSubOrgType"`
						ParentSubOrgID   interface{} `json:"parentSubOrgId"`
					} `json:"menus"`
					ReactDomain             interface{} `json:"reactDomain"`
					AppUpdatedFormattedDate string      `json:"appUpdatedFormattedDate"`
				} `json:"userContext"`
				UberSearch struct {
					MemberResults []interface{} `json:"memberResults"`
					PageResults   []interface{} `json:"pageResults"`
					Loading       bool          `json:"loading"`
				} `json:"uberSearch"`
				MemberList      []interface{} `json:"memberList"`
				MinisteringData struct {
					Loading bool `json:"loading"`
					Elders  []struct {
						Companionships []struct {
							ID        string `json:"id"`
							Ministers []struct {
								PersonUUID   string `json:"personUuid"`
								LegacyCmisID int64  `json:"legacyCmisId"`
								Name         string `json:"name"`
								NameOrder    int    `json:"nameOrder"`
								Email        string `json:"email"`
								Interviews   []struct {
									ID        string `json:"id"`
									Date      string `json:"date"`
									Timestamp string `json:"timestamp"`
								} `json:"interviews"`
								YouthBasedOnAge bool   `json:"youthBasedOnAge"`
								AssignType      string `json:"assignType,omitempty"`
								UnitOrgID       string `json:"unitOrgId,omitempty"`
							} `json:"ministers"`
							Assignments []struct {
								PersonUUID      string `json:"personUuid"`
								LegacyCmisID    int    `json:"legacyCmisId"`
								Name            string `json:"name"`
								NameOrder       int    `json:"nameOrder"`
								YouthBasedOnAge bool   `json:"youthBasedOnAge"`
							} `json:"assignments"`
							RecentlyChangedDate               string `json:"recentlyChangedDate"`
							RecentlyChangedDateInMilliseconds int64  `json:"recentlyChangedDateInMilliseconds"`
						} `json:"companionships"`
						DistrictName           string `json:"districtName"`
						DistrictUUID           string `json:"districtUuid"`
						SupervisorName         string `json:"supervisorName"`
						SupervisorLegacyCmisID int64  `json:"supervisorLegacyCmisId"`
						SupervisorPersonUUID   string `json:"supervisorPersonUuid"`
					} `json:"elders"`
					EldersQuorumSupervisors []struct {
						PersonUUID      string `json:"personUuid"`
						LegacyCmisID    int64  `json:"legacyCmisId"`
						Name            string `json:"name"`
						NameOrder       int    `json:"nameOrder"`
						YouthBasedOnAge bool   `json:"youthBasedOnAge"`
					} `json:"eldersQuorumSupervisors"`
					InterviewViewAccess             bool        `json:"interviewViewAccess"`
					Error                           bool        `json:"error"`
					ReliefSociety                   interface{} `json:"reliefSociety"`
					ReliefSocietySupervisors        interface{} `json:"reliefSocietySupervisors"`
					CurrentReliefSociety            interface{} `json:"currentReliefSociety"`
					EligibleMinistersAndAssignments struct {
						EligibleMinisters []struct {
							PersonUUID           string      `json:"personUuid"`
							LegacyCmisID         int         `json:"legacyCmisId"`
							Name                 string      `json:"name"`
							NameOrder            int         `json:"nameOrder"`
							PriesthoodOffice     string      `json:"priesthoodOffice"`
							Age                  int         `json:"age"`
							UnitOrgs             []string    `json:"unitOrgs"`
							Email                interface{} `json:"email"`
							NotGenerallyAssigned bool        `json:"notGenerallyAssigned"`
							Spouse               struct {
								EligibleMinister bool `json:"eligibleMinister"`
								NameFormats      struct {
									ListPreferredLocal   string      `json:"listPreferredLocal"`
									GivenPreferredLocal  string      `json:"givenPreferredLocal"`
									FamilyPreferredLocal string      `json:"familyPreferredLocal"`
									ListPreferred        interface{} `json:"listPreferred"`
									ListOfficial         interface{} `json:"listOfficial"`
									SpokenPreferredLocal interface{} `json:"spokenPreferredLocal"`
								} `json:"nameFormats"`
								UUID      string `json:"uuid"`
								NameOrder int    `json:"nameOrder"`
								Age       int    `json:"age"`
								Emails    []struct {
									Email     string      `json:"email"`
									OwnerType interface{} `json:"ownerType"`
									UseType   interface{} `json:"useType"`
								} `json:"emails"`
								Phones []struct {
									Number    string      `json:"number"`
									OwnerType interface{} `json:"ownerType"`
									UseType   interface{} `json:"useType"`
								} `json:"phones"`
								PhoneNumber      string      `json:"phoneNumber"`
								PriesthoodOffice string      `json:"priesthoodOffice"`
								MembershipUnit   interface{} `json:"membershipUnit"`
								LegacyCmisID     int         `json:"legacyCmisId"`
								Sex              string      `json:"sex"`
								UnitOrgsCombined []string    `json:"unitOrgsCombined"`
								Positions        interface{} `json:"positions"`
								HouseholdMember  struct {
									HouseholdRole string `json:"householdRole"`
									Household     struct {
										AnchorPerson struct {
											LegacyCmisID int    `json:"legacyCmisId"`
											UUID         string `json:"uuid"`
										} `json:"anchorPerson"`
										UUID                    string `json:"uuid"`
										FamilyNameLocal         string `json:"familyNameLocal"`
										DirectoryPreferredLocal string `json:"directoryPreferredLocal"`
										Address                 struct {
											FormattedLine1 string        `json:"formattedLine1"`
											FormattedLine2 string        `json:"formattedLine2"`
											FormattedLine3 string        `json:"formattedLine3"`
											FormattedLine4 interface{}   `json:"formattedLine4"`
											Formatted1     interface{}   `json:"formatted1"`
											Formatted2     interface{}   `json:"formatted2"`
											Formatted3     interface{}   `json:"formatted3"`
											Formatted4     interface{}   `json:"formatted4"`
											AddressLines   []string      `json:"addressLines"`
											FormattedAll   []interface{} `json:"formattedAll"`
										} `json:"address"`
										Emails []struct {
											Email     string      `json:"email"`
											OwnerType interface{} `json:"ownerType"`
											UseType   interface{} `json:"useType"`
										} `json:"emails"`
										Phones []struct {
											Number    string      `json:"number"`
											OwnerType interface{} `json:"ownerType"`
											UseType   interface{} `json:"useType"`
										} `json:"phones"`
										Unit struct {
											ParentUnit     interface{} `json:"parentUnit"`
											UUID           interface{} `json:"uuid"`
											UnitNumber     int         `json:"unitNumber"`
											NameLocal      string      `json:"nameLocal"`
											UnitType       interface{} `json:"unitType"`
											Children       interface{} `json:"children"`
											Positions      interface{} `json:"positions"`
											CdolLink       interface{} `json:"cdolLink"`
											AdminUnit      interface{} `json:"adminUnit"`
											AddressUnknown interface{} `json:"addressUnknown"`
										} `json:"unit"`
									} `json:"household"`
									MembershipUnitFlag bool `json:"membershipUnitFlag"`
								} `json:"householdMember"`
								Member                      bool   `json:"member"`
								PriesthoodTeacherOrAbove    bool   `json:"priesthoodTeacherOrAbove"`
								HouseholdEmail              string `json:"householdEmail"`
								HouseholdAnchorPersonUUID   string `json:"householdAnchorPersonUuid"`
								HouseholdNameFamilyLocal    string `json:"householdNameFamilyLocal"`
								HouseholdRole               string `json:"householdRole"`
								Convert                     bool   `json:"convert"`
								Email                       string `json:"email"`
								UnitName                    string `json:"unitName"`
								YouthBasedOnAge             bool   `json:"youthBasedOnAge"`
								IsSpouse                    bool   `json:"isSpouse"`
								HouseholdUUID               string `json:"householdUuid"`
								IsProspectiveElder          bool   `json:"isProspectiveElder"`
								IsSingleAdult               bool   `json:"isSingleAdult"`
								IsYoungSingleAdult          bool   `json:"isYoungSingleAdult"`
								HouseholdPhoneNumber        string `json:"householdPhoneNumber"`
								IsHead                      bool   `json:"isHead"`
								PersonUUID                  string `json:"personUuid"`
								NameListPreferredLocal      string `json:"nameListPreferredLocal"`
								HouseholdNameDirectoryLocal string `json:"householdNameDirectoryLocal"`
								FormattedAddress            string `json:"formattedAddress"`
								UnitNumber                  int    `json:"unitNumber"`
								NameGivenPreferredLocal     string `json:"nameGivenPreferredLocal"`
								IsMember                    bool   `json:"isMember"`
								HouseHoldMemberNameForList  string `json:"houseHoldMemberNameForList"`
								IsOutOfUnitMember           bool   `json:"isOutOfUnitMember"`
								IsAdult                     bool   `json:"isAdult"`
								NameFamilyPreferredLocal    string `json:"nameFamilyPreferredLocal"`
								OutOfUnitMember             bool   `json:"outOfUnitMember"`
								Address                     struct {
									FormattedLine1 string        `json:"formattedLine1"`
									FormattedLine2 string        `json:"formattedLine2"`
									FormattedLine3 string        `json:"formattedLine3"`
									FormattedLine4 interface{}   `json:"formattedLine4"`
									Formatted1     interface{}   `json:"formatted1"`
									Formatted2     interface{}   `json:"formatted2"`
									Formatted3     interface{}   `json:"formatted3"`
									Formatted4     interface{}   `json:"formatted4"`
									AddressLines   []string      `json:"addressLines"`
									FormattedAll   []interface{} `json:"formattedAll"`
								} `json:"address"`
								Birth struct {
									Date struct {
										Date    string `json:"date"`
										Calc    string `json:"calc"`
										Display string `json:"display"`
									} `json:"date"`
									MonthDay struct {
										Date    string `json:"date"`
										Calc    string `json:"calc"`
										Display string `json:"display"`
									} `json:"monthDay"`
									Place   interface{} `json:"place"`
									Country interface{} `json:"country"`
								} `json:"birth"`
								PersonStatusFlags struct {
									Member           bool `json:"member"`
									Convert          bool `json:"convert"`
									Adult            bool `json:"adult"`
									SingleAdult      bool `json:"singleAdult"`
									YoungSingleAdult bool `json:"youngSingleAdult"`
									ProspectiveElder bool `json:"prospectiveElder"`
									Deceased         bool `json:"deceased"`
								} `json:"personStatusFlags"`
								Name string `json:"name"`
							} `json:"spouse,omitempty"`
						} `json:"eligibleMinisters"`
						EligibleAssignments []struct {
							PersonUUID   string      `json:"personUuid"`
							LegacyCmisID int         `json:"legacyCmisId"`
							Name         string      `json:"name"`
							NameOrder    int         `json:"nameOrder"`
							Email        interface{} `json:"email"`
						} `json:"eligibleAssignments"`
					} `json:"eligibleMinistersAndAssignments"`
				} `json:"ministeringData"`
				MinisteringStats struct {
				} `json:"ministeringStats"`
				MinisteringAssignmentsReport struct {
				} `json:"ministeringAssignmentsReport"`
				MemberCard struct {
					Loading        bool        `json:"loading"`
					Error          interface{} `json:"error"`
					MemberCardList struct {
					} `json:"memberCardList"`
				} `json:"memberCard"`
				HouseholdCard struct {
					Loading           bool        `json:"loading"`
					Error             interface{} `json:"error"`
					HouseholdCardList struct {
					} `json:"householdCardList"`
				} `json:"householdCard"`
				MinisteringFilter struct {
					SearchText        string `json:"searchText"`
					SelectedDistricts struct {
						All bool `json:"all"`
					} `json:"selectedDistricts"`
					SelectedOptionFilters struct {
					} `json:"selectedOptionFilters"`
					SelectedOrg              string `json:"selectedOrg"`
					SelectedQuarter          string `json:"selectedQuarter"`
					UnassignedSearchText     string `json:"unassignedSearchText"`
					ShowNotGenerallyAssigned bool   `json:"showNotGenerallyAssigned"`
				} `json:"ministeringFilter"`
				UnitPicker struct {
					SelectedUnit struct {
					} `json:"selectedUnit"`
					SearchValue interface{} `json:"searchValue"`
				} `json:"unitPicker"`
				MinisteringAssignmentsFilter struct {
					HouseholdsSearchText         string `json:"householdsSearchText"`
					HouseholdsAssignedValue      string `json:"householdsAssignedValue"`
					HouseholdsOrganizationsValue string `json:"householdsOrganizationsValue"`
					IndividualsSearchText        string `json:"individualsSearchText"`
					IndividualsOrgFilter         string `json:"individualsOrgFilter"`
					IndividualsMissingFilter     string `json:"individualsMissingFilter"`
				} `json:"ministeringAssignmentsFilter"`
				MinisteringPhotos struct {
				} `json:"ministeringPhotos"`
				NewMemberReport struct {
					Data       []interface{} `json:"data"`
					SearchText string        `json:"searchText"`
					Term       string        `json:"term"`
					ShowAge    bool          `json:"showAge"`
					IsLoading  bool          `json:"isLoading"`
				} `json:"newMemberReport"`
				BirthdayList struct {
					Data        []interface{} `json:"data"`
					MonthsShown string        `json:"monthsShown"`
					UnitOrg     string        `json:"unitOrg"`
					Month       int           `json:"month"`
					ShowAge     bool          `json:"showAge"`
					IsLoading   bool          `json:"isLoading"`
				} `json:"birthdayList"`
				SacramentAttendanceReport struct {
					Data          []interface{} `json:"data"`
					Years         []interface{} `json:"years"`
					Year          interface{}   `json:"year"`
					EditMonth     interface{}   `json:"editMonth"`
					IsSaving      bool          `json:"isSaving"`
					IsLoading     bool          `json:"isLoading"`
					IsInitialLoad bool          `json:"isInitialLoad"`
					TempWeeks     []interface{} `json:"tempWeeks"`
				} `json:"sacramentAttendanceReport"`
			} `json:"initialState"`
			InitialProps struct {
				Type       string `json:"type"`
				UnitNumber int    `json:"unitNumber"`
				Tab        string `json:"tab"`
			} `json:"initialProps"`
		} `json:"pageProps"`
	} `json:"props"`
	Page  string `json:"page"`
	Query struct {
		Lang string `json:"lang"`
		Tab  string `json:"tab"`
		Type string `json:"type"`
	} `json:"query"`
	BuildID      string          `json:"buildId"`
	IsFallback   bool            `json:"isFallback"`
	CustomServer bool            `json:"customServer"`
	Gip          bool            `json:"gip"`
	Head         [][]interface{} `json:"head"`
}

About

Unofficial tool to send a friendly email for all assignments with plain-text contact information

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published