charlesv2 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of charlesv2 might be problematic. Click here for more details.
- package/README.md +35 -0
- package/charles.bat +3 -0
- package/configCharles.js +43 -0
- package/connectToChurch/averageFilter.js +22 -0
- package/connectToChurch/cookieHandler.js +43 -0
- package/connectToChurch/getAverage.js +121 -0
- package/connectToChurch/getBearer.js +55 -0
- package/connectToChurch/listToday.js +49 -0
- package/connectToChurch/sneakyChurch.js +132 -0
- package/connectToChurch/superParse.js +27 -0
- package/connectToFacebook/crazyJob.json +10 -0
- package/connectToFacebook/editPayload.js +52 -0
- package/connectToFacebook/kindMessages.json +36 -0
- package/connectToFacebook/prettyString.js +25 -0
- package/connectToFacebook/randomMessage.js +8 -0
- package/connectToFacebook/sneakyFacebook.js +215 -0
- package/createConfig.js +46 -0
- package/createPayload.js +54 -0
- package/getZone.js +103 -0
- package/index.js +123 -0
- package/lastRun.js +24 -0
- package/package.json +28 -0
- package/prettyStringZones.js +18 -0
- package/smlReport.js +143 -0
package/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
# CHARLESV3
|
3
|
+
|
4
|
+
If you don't know what this code does, just forget about it.
|
5
|
+
|
6
|
+
## Why JS?
|
7
|
+
|
8
|
+
> JavaScript is the only language that I'm aware of that people feel they don't need to learn before they start using it. --Douglas Crockford
|
9
|
+
|
10
|
+
JavaScript (or JS) is one of the only truly universal programming languages. It's easy to use and simple to read. For that reason I rewrote the Holly System into JavaScript.
|
11
|
+
|
12
|
+
## Why Not TypeScript?
|
13
|
+
|
14
|
+
...I have yet to learn TypeScript. I would love to, but I'm a missionary and don't have time for silly things like that.
|
15
|
+
|
16
|
+
## How do I...?
|
17
|
+
|
18
|
+
HA, nice try. If you're supposed to use this, you'll have received a document with instructions and videos for setup.
|
19
|
+
|
20
|
+
AUTHORS NOTE: if you already know how to do everything and want a smoother running experience, double click the charles.bat file
|
21
|
+
|
22
|
+
## TODO
|
23
|
+
|
24
|
+
- [x] Set up basic framework
|
25
|
+
- [x] Create pseudo-human input for websites
|
26
|
+
- [x] Create a CLI to interact with Charles (this particular instance of Holly)
|
27
|
+
- [ ] Reformat ugly code
|
28
|
+
- [x] Refactor fetching
|
29
|
+
- [ ] Wrap in electron.js interface?
|
30
|
+
- [ ] Make shareable across missions
|
31
|
+
- [ ] Increase efficiency
|
32
|
+
|
33
|
+
# CREDIT
|
34
|
+
|
35
|
+
Original credit for the Holly system goes to @jkcoxson (github)
|
package/charles.bat
ADDED
package/configCharles.js
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
import chalk from "chalk"
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "fs"
|
3
|
+
import prompts from "prompts"
|
4
|
+
import { getZone } from './getZone.js'
|
5
|
+
import { createConfig } from "./createConfig.js"
|
6
|
+
|
7
|
+
|
8
|
+
function checkConfig(configPath) {
|
9
|
+
try {
|
10
|
+
if (!existsSync(configPath)) {
|
11
|
+
throw new Error("Config file not found.");
|
12
|
+
}
|
13
|
+
return JSON.parse(readFileSync(configPath));
|
14
|
+
// eslint-disable-next-line no-unused-vars
|
15
|
+
} catch (e) {
|
16
|
+
return null;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
|
20
|
+
export async function configCharles(configPath) {
|
21
|
+
if (!existsSync(configPath.replace('/charlesConfig.json', '/config.json'))) {
|
22
|
+
await createConfig('./resources/config.json')
|
23
|
+
}
|
24
|
+
if (checkConfig(configPath)) {
|
25
|
+
return await JSON.parse(readFileSync(configPath))
|
26
|
+
} else {
|
27
|
+
console.clear()
|
28
|
+
console.log(chalk.dim("You don't seem to have set up Charles yet, let's do that now!\n"))
|
29
|
+
console.log(chalk.cyanBright("You're going to be asked for the Zone Chat ID of your groups.\nYou can find them in the Messenger Website ") + chalk.dim("(EX https://www.messenger.com/t/") + chalk.cyanBright("2276304299063254") + chalk.dim("<-- ID)"))
|
30
|
+
const zoneArray = await getZone()
|
31
|
+
let questions = []
|
32
|
+
for (let i = 0; i < zoneArray.length; i++) {
|
33
|
+
questions.push({
|
34
|
+
type: 'text',
|
35
|
+
name: zoneArray[i],
|
36
|
+
message: `What is the Zone Chat ID for ${zoneArray[i]}?`
|
37
|
+
});
|
38
|
+
}
|
39
|
+
const charlesConfig = await prompts(questions)
|
40
|
+
writeFileSync('resources/charlesConfig.json', JSON.stringify(charlesConfig))
|
41
|
+
return charlesConfig
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
function isRecent(person) {
|
2
|
+
const now = Date.now(); // Get the current timestamp in milliseconds
|
3
|
+
const twoWeeksAgo = now - (14 * 24 * 60 * 60 * 1000); // Subtract 2 weeks in milliseconds
|
4
|
+
const twelveHoursAgo = now - (12 * 60 * 60 * 1000); // Subtract 12 hours in milliseconds
|
5
|
+
|
6
|
+
// Check if referral date is between 12 hours ago and 2 weeks ago
|
7
|
+
return person.referralAssignedDate > twoWeeksAgo && person.referralAssignedDate <= twelveHoursAgo;
|
8
|
+
}
|
9
|
+
|
10
|
+
|
11
|
+
export function averageFilter(thing) {
|
12
|
+
return thing.reduce((acc, person) => {
|
13
|
+
if (isRecent(person)) {
|
14
|
+
acc.push({
|
15
|
+
guid: person.personGuid,
|
16
|
+
zoneName: person.zoneName,
|
17
|
+
areaName: person.areaName
|
18
|
+
});
|
19
|
+
}
|
20
|
+
return acc;
|
21
|
+
}, []);
|
22
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
import { promises as fs } from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
|
4
|
+
// Helper to get the current directory from the file URL
|
5
|
+
// Helper to get the current directory from the file URL
|
6
|
+
const getCurrentDir = () => {
|
7
|
+
const fileUrl = new URL(import.meta.url);
|
8
|
+
let pathname = fileUrl.pathname;
|
9
|
+
|
10
|
+
// If pathname starts with a leading slash, remove it for Windows compatibility
|
11
|
+
if (pathname.startsWith('/')) {
|
12
|
+
pathname = pathname.substring(1);
|
13
|
+
}
|
14
|
+
|
15
|
+
return path.dirname(pathname);
|
16
|
+
};
|
17
|
+
|
18
|
+
// Returns true if cookies exist and are applied, false if login is needed
|
19
|
+
export async function cookieHandler(page) {
|
20
|
+
try {
|
21
|
+
const currentDir = getCurrentDir();
|
22
|
+
const filePath = path.resolve(currentDir, '..', 'resources', 'cookies.json'); // Properly resolve relative path
|
23
|
+
const cookies = await fs.readFile(filePath, 'utf8').then(JSON.parse); // Using fs.readFile instead of fetch
|
24
|
+
|
25
|
+
await page.setCookie(...cookies);
|
26
|
+
return true;
|
27
|
+
} catch (error) {
|
28
|
+
console.error('Failed to load cookies:', error.message);
|
29
|
+
return false;
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
// Saves cookies to file
|
34
|
+
export async function saveCookies(cookie) {
|
35
|
+
try {
|
36
|
+
const currentDir = getCurrentDir();
|
37
|
+
|
38
|
+
const filePath = path.resolve(currentDir, '..', 'resources', 'cookies.json'); // Properly resolve relative path
|
39
|
+
await fs.writeFile(filePath, JSON.stringify(cookie, null, 2));
|
40
|
+
} catch (error) {
|
41
|
+
console.error('Writing cookies failed:', error.message);
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,121 @@
|
|
1
|
+
import chalk from "chalk";
|
2
|
+
import cliProgress from 'cli-progress'
|
3
|
+
import { averageFilter } from "./averageFilter.js";
|
4
|
+
import Bottleneck from "bottleneck";
|
5
|
+
|
6
|
+
|
7
|
+
function formatTime(minutes) {
|
8
|
+
const days = Math.floor(minutes / 1440); // 1 day = 1440 minutes
|
9
|
+
const hours = Math.floor((minutes % 1440) / 60); // Remaining hours
|
10
|
+
const mins = Math.floor(minutes % 60); // Remaining minutes
|
11
|
+
|
12
|
+
let result = [];
|
13
|
+
if (days > 0) result.push(`${days} day${days > 1 ? 's' : ''}`);
|
14
|
+
if (hours > 0) result.push(`${hours} hr${hours > 1 ? 's' : ''}`);
|
15
|
+
if (mins > 0 || result.length === 0) result.push(`${mins} min${mins > 1 ? 's' : ''}`);
|
16
|
+
|
17
|
+
return result.join(" ");
|
18
|
+
}
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
async function processContactTime(timeline) {
|
25
|
+
const reversedTimeline = [...timeline].reverse();
|
26
|
+
|
27
|
+
let referralSent = null;
|
28
|
+
let lastContact = null;
|
29
|
+
|
30
|
+
for (const item of reversedTimeline) {
|
31
|
+
switch (item.timelineItemType) {
|
32
|
+
case "NEW_REFERRAL":
|
33
|
+
referralSent = new Date(item.itemDate);
|
34
|
+
lastContact = null; // Reset when a new referral is found
|
35
|
+
break;
|
36
|
+
case "CONTACT":
|
37
|
+
case "TEACHING":
|
38
|
+
if (!lastContact) {
|
39
|
+
lastContact = new Date(item.itemDate);
|
40
|
+
}
|
41
|
+
break;
|
42
|
+
default:
|
43
|
+
continue;
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
if (referralSent && lastContact) {
|
48
|
+
const duration = (lastContact - referralSent) / (1000 * 60); // Convert milliseconds to minutes
|
49
|
+
return Math.floor(duration);
|
50
|
+
}
|
51
|
+
|
52
|
+
return null;
|
53
|
+
}
|
54
|
+
|
55
|
+
async function contactTimeUnhinged(guid, page, bar, unprocessedContacts, person) {
|
56
|
+
// await delay(500); // Wait .5 second between requests
|
57
|
+
const response = await page.evaluate(async (guid) => {
|
58
|
+
const url = `https://referralmanager.churchofjesuschrist.org/services/progress/timeline/${guid}`;
|
59
|
+
const response = await fetch(url, {method: 'GET'} )
|
60
|
+
return await response.json()
|
61
|
+
}, guid)
|
62
|
+
|
63
|
+
bar.increment()
|
64
|
+
|
65
|
+
const time = await processContactTime(response)
|
66
|
+
if (!unprocessedContacts[person.zoneName]) {
|
67
|
+
unprocessedContacts[person.zoneName] = [];
|
68
|
+
}
|
69
|
+
unprocessedContacts[person.zoneName].push(time);
|
70
|
+
|
71
|
+
}
|
72
|
+
|
73
|
+
const limiter = new Bottleneck({
|
74
|
+
maxConcurrent: 10,
|
75
|
+
})
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
export async function getAverage(wholeShebang, page) {
|
80
|
+
const bar = new cliProgress.SingleBar({
|
81
|
+
format: 'DigiStalking People |' + chalk.cyan('{bar}') + '| {percentage}% || {value}/{total} People || ETA: {eta_formatted}',
|
82
|
+
barCompleteChar: '\u2588',
|
83
|
+
barIncompleteChar: '\u2591',
|
84
|
+
hideCursor: true
|
85
|
+
});
|
86
|
+
|
87
|
+
let parsed = await averageFilter(wholeShebang);
|
88
|
+
let unprocessedContacts = {};
|
89
|
+
|
90
|
+
bar.start(parsed.length, 0);
|
91
|
+
|
92
|
+
let tasks = parsed
|
93
|
+
.filter(person => person.guid) // Ignore people without a GUID
|
94
|
+
.map(person =>
|
95
|
+
limiter.schedule(() =>
|
96
|
+
contactTimeUnhinged(person.guid, page, bar, unprocessedContacts, person)
|
97
|
+
)
|
98
|
+
);
|
99
|
+
await Promise.all(tasks)
|
100
|
+
|
101
|
+
bar.stop();
|
102
|
+
|
103
|
+
// Remove empty zones
|
104
|
+
delete unprocessedContacts[null];
|
105
|
+
delete unprocessedContacts[undefined];
|
106
|
+
delete unprocessedContacts[""];
|
107
|
+
delete unprocessedContacts['Dothan']
|
108
|
+
|
109
|
+
// Compute and sort averages
|
110
|
+
let zoneAverages = Object.entries(unprocessedContacts)
|
111
|
+
.map(([zone, times]) => ({ zone: zone.trim(), avgTime: times.reduce((sum, t) => sum + (t || 0), 0) / times.length })) // Compute numeric average
|
112
|
+
.sort((a, b) => a.avgTime - b.avgTime); // Sort by shortest average contact time
|
113
|
+
|
114
|
+
// Construct message
|
115
|
+
let message = "-->Contact Time<--\n";
|
116
|
+
for (const { zone, avgTime } of zoneAverages) {
|
117
|
+
message += `↳ ${zone}: ${formatTime(avgTime)}\n`;
|
118
|
+
}
|
119
|
+
|
120
|
+
return message;
|
121
|
+
}
|
@@ -0,0 +1,55 @@
|
|
1
|
+
import { promises as fs } from "fs";
|
2
|
+
import path from "path";
|
3
|
+
|
4
|
+
// Helper function to resolve the absolute path to bearer.txt
|
5
|
+
// Function to get the bearer token
|
6
|
+
|
7
|
+
// Helper to get the current directory from the file URL
|
8
|
+
const getCurrentDir = () => {
|
9
|
+
const fileUrl = new URL(import.meta.url);
|
10
|
+
let pathname = fileUrl.pathname;
|
11
|
+
|
12
|
+
// If pathname starts with a leading slash, remove it for Windows compatibility
|
13
|
+
if (pathname.startsWith('/')) {
|
14
|
+
pathname = pathname.substring(1);
|
15
|
+
}
|
16
|
+
|
17
|
+
return path.dirname(pathname);
|
18
|
+
};
|
19
|
+
|
20
|
+
export async function getBearer(page) {
|
21
|
+
|
22
|
+
let bearer
|
23
|
+
const currentDir = getCurrentDir();
|
24
|
+
const filePath = path.resolve(currentDir, '..', 'resources', 'bearer.txt'); // Resolves to the correct path
|
25
|
+
try {
|
26
|
+
bearer = await fs.readFile(filePath, 'utf8'); // Using fs.readFile instead of fetch
|
27
|
+
// eslint-disable-next-line no-unused-vars
|
28
|
+
} catch (e) {
|
29
|
+
bearer = null
|
30
|
+
}
|
31
|
+
|
32
|
+
if (!bearer) {
|
33
|
+
try {
|
34
|
+
// Fetch authentication data from the server
|
35
|
+
const missionaryObj = await page.evaluate(async () => {
|
36
|
+
const response = await fetch("https://referralmanager.churchofjesuschrist.org/services/auth");
|
37
|
+
return await response.json(); // Ensure it's parsed as JSON
|
38
|
+
});
|
39
|
+
|
40
|
+
if (!missionaryObj.token) {
|
41
|
+
throw new Error("No token found in response");
|
42
|
+
}
|
43
|
+
|
44
|
+
// Save the token to bearer.txt
|
45
|
+
await fs.writeFile(filePath, missionaryObj.token, "utf-8");
|
46
|
+
|
47
|
+
bearer = missionaryObj.token;
|
48
|
+
} catch (error) {
|
49
|
+
console.error("Error fetching bearer token:", error.message);
|
50
|
+
return null; // Return null to indicate failure
|
51
|
+
}
|
52
|
+
}
|
53
|
+
|
54
|
+
return bearer;
|
55
|
+
}
|
@@ -0,0 +1,49 @@
|
|
1
|
+
function isWithinTimeRange(dateString) {
|
2
|
+
const givenDate = new Date(dateString);
|
3
|
+
const now = new Date();
|
4
|
+
|
5
|
+
// Calculate the time boundaries
|
6
|
+
const fortyEightHoursAgo = new Date(now);
|
7
|
+
fortyEightHoursAgo.setHours(now.getHours() - 2);
|
8
|
+
|
9
|
+
const fourteenDaysAgo = new Date(now);
|
10
|
+
fourteenDaysAgo.setDate(now.getDate() - 14);
|
11
|
+
|
12
|
+
// Check if the given date is within the range
|
13
|
+
return givenDate >= fourteenDaysAgo && givenDate <= fortyEightHoursAgo;
|
14
|
+
}
|
15
|
+
|
16
|
+
function isGreenOrYellow(obj) {
|
17
|
+
if (obj.personStatus === 1 || obj.personStatus === 2 || obj.personStatus === 3 || obj.personStatus === 4) {
|
18
|
+
return true
|
19
|
+
} else {
|
20
|
+
return false
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
function unattempted(obj) {
|
25
|
+
return (obj.referralStatus === 10)
|
26
|
+
}
|
27
|
+
|
28
|
+
function unsuccessful(obj) {
|
29
|
+
return (obj.referralStatus === 20)
|
30
|
+
}
|
31
|
+
|
32
|
+
export async function listToday(list) {
|
33
|
+
let listFinal = []
|
34
|
+
if (!Array.isArray(list) && typeof list === 'object') {
|
35
|
+
for (const key in list) {
|
36
|
+
if (isWithinTimeRange(list[key].assignedDate) && isGreenOrYellow(list[key]) && (unattempted(list[key]) || unsuccessful(list[key]))) {
|
37
|
+
listFinal.push(list[key])
|
38
|
+
}
|
39
|
+
}
|
40
|
+
} else {
|
41
|
+
let todaysList = list.filter(obj => isWithinTimeRange(obj.assignedDate))
|
42
|
+
const todaysListWithoutGrey = todaysList.filter(obj => isGreenOrYellow(obj))
|
43
|
+
listFinal = todaysListWithoutGrey.filter(obj =>{
|
44
|
+
return (unattempted(obj) || unsuccessful(obj))
|
45
|
+
})
|
46
|
+
}
|
47
|
+
|
48
|
+
return listFinal
|
49
|
+
}
|
@@ -0,0 +1,132 @@
|
|
1
|
+
import puppeteer from "puppeteer"
|
2
|
+
import path from 'node:path'
|
3
|
+
import { cookieHandler, saveCookies } from "./cookieHandler.js"
|
4
|
+
import { readFileSync, writeFileSync } from "fs"
|
5
|
+
import { jwtDecode } from "jwt-decode"
|
6
|
+
import { getAverage } from "./getAverage.js"
|
7
|
+
import { superParse } from "./superParse.js"
|
8
|
+
import { listToday } from "./listToday.js"
|
9
|
+
import ora from "ora"
|
10
|
+
import { getBearer } from "./getBearer.js"
|
11
|
+
|
12
|
+
function isMoreThanADayOld(timestamp) {
|
13
|
+
const oneDay = 24 * 60 * 60 * 1000
|
14
|
+
const now = Date.now()
|
15
|
+
return now - timestamp > oneDay
|
16
|
+
}
|
17
|
+
|
18
|
+
export async function login(user, pass, page) {
|
19
|
+
// Enter username
|
20
|
+
await page.goto('https://referralmanager.churchofjesuschrist.org/')
|
21
|
+
await page.waitForSelector("input[name='identifier']", { timeout: 10000 });
|
22
|
+
const username = await page.$("input[name='identifier']")
|
23
|
+
await username.type(user)
|
24
|
+
await page.click("input[type='submit']")
|
25
|
+
|
26
|
+
// Enter password
|
27
|
+
await page.waitForSelector("input[name='credentials.passcode']", {timeout: 10000})
|
28
|
+
await page.type("input[name='credentials.passcode']", pass)
|
29
|
+
await page.click("input[type='submit']")
|
30
|
+
await page.waitForNavigation()
|
31
|
+
// get cookies and save
|
32
|
+
const cookies = await page.cookies()
|
33
|
+
await saveCookies(cookies)
|
34
|
+
|
35
|
+
}
|
36
|
+
|
37
|
+
// False == should not pull
|
38
|
+
async function toPullOrNotToPullThatIsTheQuestion(pathToHome) {
|
39
|
+
try {
|
40
|
+
let thiccList = readFileSync(path.join(pathToHome, 'resources', 'people.json'), 'utf-8')
|
41
|
+
let thiccJSON = await JSON.parse(thiccList)
|
42
|
+
let rawList = readFileSync(path.join(pathToHome, 'resources', 'rawList.json'), 'utf-8')
|
43
|
+
let rawJSON = await JSON.parse(rawList)
|
44
|
+
if (!isMoreThanADayOld(thiccJSON.processedTime) && !isMoreThanADayOld(rawJSON.processedTime)) {
|
45
|
+
return false
|
46
|
+
}
|
47
|
+
else {
|
48
|
+
return true
|
49
|
+
}
|
50
|
+
|
51
|
+
} catch (e) {
|
52
|
+
console.log(e)
|
53
|
+
return true
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
export async function getPeopleList(page, bearer, decodedBearer) {
|
58
|
+
const list = await page.evaluate(async (decodedBearer, bearer) => {
|
59
|
+
const peopleList = await fetch(`https://referralmanager.churchofjesuschrist.org/services/people/mission/${JSON.stringify(decodedBearer.missionId)}?includeDroppedPersons=true`, {
|
60
|
+
method: 'GET',
|
61
|
+
headers: {
|
62
|
+
'Authorization' : `Bearer ${bearer}`
|
63
|
+
}
|
64
|
+
})
|
65
|
+
const list = await peopleList.json()
|
66
|
+
return list
|
67
|
+
}, decodedBearer, bearer)
|
68
|
+
return list
|
69
|
+
}
|
70
|
+
|
71
|
+
export async function sneakyChurch(user, pass, pathToHome="", headless=true) {
|
72
|
+
console.clear()
|
73
|
+
let spool = ora('Opening browser').start()
|
74
|
+
// Launch browser and use cookies from previous session if possible.
|
75
|
+
const browser = await puppeteer.launch({ headless:headless })
|
76
|
+
const page = await browser.newPage()
|
77
|
+
spool.color = 'magenta'
|
78
|
+
spool.text = "Doin' some black magic"
|
79
|
+
const okayToSkipLogin = await cookieHandler(page, pathToHome)
|
80
|
+
if (okayToSkipLogin) {
|
81
|
+
await page.goto('https://referralmanager.churchofjesuschrist.org/')
|
82
|
+
const isLoggedOut = await page.$("input[name='identifier']")
|
83
|
+
if (isLoggedOut) {
|
84
|
+
await login(user, pass, page)
|
85
|
+
}
|
86
|
+
} else {
|
87
|
+
await login(user, pass, page)
|
88
|
+
}
|
89
|
+
spool.color = 'green'
|
90
|
+
spool.text = 'Stealing your identity'
|
91
|
+
// Snag the bearer token *enters hacker mode*
|
92
|
+
const bearer = await getBearer(page)
|
93
|
+
const decodedBearer = jwtDecode(bearer)
|
94
|
+
let lossyList
|
95
|
+
let todaysList
|
96
|
+
let beginPackage
|
97
|
+
|
98
|
+
// Get new list if we don't have one cached
|
99
|
+
if (await toPullOrNotToPullThatIsTheQuestion(pathToHome)) {
|
100
|
+
spool.color = 'cyan'
|
101
|
+
spool.text = 'Fetching referrals'
|
102
|
+
const fullListObj = await getPeopleList(page, bearer, decodedBearer)
|
103
|
+
|
104
|
+
writeFileSync(path.join(pathToHome, 'resources', 'rawList.json'), JSON.stringify(fullListObj))
|
105
|
+
spool.info('Getting Average contact times!')
|
106
|
+
|
107
|
+
beginPackage = await getAverage(fullListObj.persons, page)
|
108
|
+
|
109
|
+
lossyList = await superParse(fullListObj)
|
110
|
+
todaysList = await listToday(lossyList)
|
111
|
+
writeFileSync(path.join(pathToHome, 'resources', 'people.json'), JSON.stringify(
|
112
|
+
{ 'processedTime' : Date.now(),
|
113
|
+
'persons' : todaysList
|
114
|
+
}
|
115
|
+
))
|
116
|
+
spool.succeed('Referrals retrieved')
|
117
|
+
await browser.close()
|
118
|
+
return [todaysList, beginPackage]
|
119
|
+
} else {
|
120
|
+
lossyList = await JSON.parse(readFileSync(path.join(pathToHome, 'resources', 'people.json')))
|
121
|
+
let rawList = await JSON.parse(readFileSync(path.join(pathToHome, 'resources', 'rawList.json')))
|
122
|
+
spool.info('Snooping out Average contact times!')
|
123
|
+
|
124
|
+
beginPackage = await getAverage(rawList.persons, page)
|
125
|
+
todaysList = await listToday(lossyList.persons)
|
126
|
+
spool.succeed('Referrals retrieved')
|
127
|
+
await browser.close()
|
128
|
+
return [todaysList, beginPackage]
|
129
|
+
}
|
130
|
+
|
131
|
+
}
|
132
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
export function superParse(data) {
|
2
|
+
if (!data || !Array.isArray(data.persons)) {
|
3
|
+
return [];
|
4
|
+
}
|
5
|
+
|
6
|
+
return data.persons
|
7
|
+
.map(person => {
|
8
|
+
try {
|
9
|
+
return {
|
10
|
+
guid: person.personGuid,
|
11
|
+
name: person.lastName ? `${person.firstName} ${person.lastName}` : person.firstName,
|
12
|
+
referralStatus: person.referralStatusId,
|
13
|
+
personStatus: person.personStatusId,
|
14
|
+
missionId: person.missionId,
|
15
|
+
zoneId: person.zoneId ?? null,
|
16
|
+
zoneName: person.zoneName ?? null,
|
17
|
+
districtId: person.districtId ?? null,
|
18
|
+
areaName: person.areaName ?? null,
|
19
|
+
assignedDate: person.referralAssignedDate,
|
20
|
+
};
|
21
|
+
// eslint-disable-next-line no-unused-vars
|
22
|
+
} catch (error) {
|
23
|
+
return null;
|
24
|
+
}
|
25
|
+
})
|
26
|
+
.filter(person => person !== null);
|
27
|
+
};
|
@@ -0,0 +1,10 @@
|
|
1
|
+
[
|
2
|
+
"BOOYAH!!! Y'all don't have any uncontacted referrals\nᕕ(⌐■_■)ᕗ ♪♬",
|
3
|
+
"Nice work on contacting all of your referrals! Keep the work up! \nᕕ( ᐛ )ᕗ",
|
4
|
+
"How did y'all get so cool? Wait, probably by contacting ALL OF YOUR REFERRALS?\n ¯\\_(ツ)_/¯",
|
5
|
+
"I'm sure your referrals will thank you for contacting them, and just in case they don't I'll say it myself.\nThank you.\n\n\\(^-^)/",
|
6
|
+
"Referrals contacted? ✅\nThe work hastens? ✅\nCharles approves? ✅\nLET'S GO!! 🚀",
|
7
|
+
"🔥 Y'ALL DID IT! No uncontacted referrals left! 🔥 Absolute legends! 🚀",
|
8
|
+
"🎉 100% CONTACT RATE ACHIEVED! 🎯 Y'all are on fire! Keep that energy up! 💪",
|
9
|
+
"📢 Attention world: These missionaries just reached 100% contacted referrals! Y’all are unstoppable! 🔥"
|
10
|
+
]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
2
|
+
import { getZone } from "../getZone.js";
|
3
|
+
|
4
|
+
export async function editPayload() {
|
5
|
+
let payload;
|
6
|
+
|
7
|
+
try {
|
8
|
+
payload = JSON.parse(readFileSync('payload.json', 'utf8'));
|
9
|
+
} catch (err) {
|
10
|
+
console.error(err);
|
11
|
+
return;
|
12
|
+
}
|
13
|
+
|
14
|
+
let zoneObj;
|
15
|
+
|
16
|
+
if (existsSync('resources/charlesConfig.json')) {
|
17
|
+
try {
|
18
|
+
zoneObj = await JSON.parse(readFileSync('resources/charlesConfig.json', 'utf8'));
|
19
|
+
} catch (err) {
|
20
|
+
console.error(err);
|
21
|
+
return;
|
22
|
+
}
|
23
|
+
} else {
|
24
|
+
zoneObj = await getZone();
|
25
|
+
}
|
26
|
+
|
27
|
+
const prettyList = {};
|
28
|
+
prettyList["avg"] = payload.average
|
29
|
+
|
30
|
+
for (const zone in zoneObj) {
|
31
|
+
const zoneClean = zone.trim().toString()
|
32
|
+
const areaMap = {}; // Will hold areaName as key, and people array as value
|
33
|
+
if (!payload.payload[zoneClean]) {
|
34
|
+
continue
|
35
|
+
}
|
36
|
+
for (const person of payload.payload[zoneClean]) {
|
37
|
+
if (zone.toLowerCase().trim() === person.zoneName.toLowerCase().trim()) {
|
38
|
+
const areaName = person.areaName;
|
39
|
+
// Initialize an array for the area if it doesn't exist yet
|
40
|
+
if (!areaMap[areaName]) {
|
41
|
+
areaMap[areaName] = { people: [] };
|
42
|
+
}
|
43
|
+
// Add the person's name to the appropriate area
|
44
|
+
areaMap[areaName].people.push("· " + person.name); // Assuming 'name' is the key for person names
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
prettyList[zoneClean.toString()] = areaMap;
|
49
|
+
|
50
|
+
}
|
51
|
+
return prettyList
|
52
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
{
|
2
|
+
"messages": [
|
3
|
+
"You know who’s great at answering calls? The Lord. You know who else should be? Your unsuccessfully contacted referrals. But first, they need a message!",
|
4
|
+
"Your unsuccessfully contacted referrals are like manna—best gathered fresh! Don’t let them go stale. Go say hi!",
|
5
|
+
"Hey y'all, if I had a dime for every unsuccessfully contacted referral, I’d have… well, a handful of dimes. Let’s change that today!",
|
6
|
+
"Good news! Your unsuccessfully contacted referrals are still waiting. Bad news? They won’t reach out first. But you got this!",
|
7
|
+
"Imagine me waving a giant foam finger—‘You’re #1 at contacting unsuccessfully contacted referrals!’ Now go prove me right!",
|
8
|
+
"Your unsuccessfully contacted referrals are like unopened Christmas presents! Time to unwrap some gospel joy!",
|
9
|
+
"I believe in you. More importantly, *He* believes in you. Now go turn those ‘uncontacted’ into ‘new friends’!",
|
10
|
+
"Fun fact: Missionary joy is 87% higher when you contact unsuccessfully contacted referrals. (Okay, I made that up, but it sounds legit, right?)",
|
11
|
+
"Your unsuccessfully contacted referrals are golden investigators, but first, you gotta *dig*—aka send that first message!",
|
12
|
+
"The field is white, already to harvest… but you still gotta grab the sickle (aka your phone). Let’s get messaging!",
|
13
|
+
"Remember, every great conversion story starts with a simple ‘Hello!’ Time to make history!",
|
14
|
+
"A message today could be the start of someone’s journey toward eternal joy. Don’t leave them waiting!",
|
15
|
+
"You have the best news in the world—why keep it to yourself? Let’s spread some light!",
|
16
|
+
"Your kindness and testimony are exactly what your unsuccessfully contacted referrals need. Be a little boulder and reach out!",
|
17
|
+
"Hey zone, don’t let your unsuccessfully contacted referrals become legendary ghost stories. Time to make contact!",
|
18
|
+
"You’re planting seeds of faith—every message is a step toward miracles!",
|
19
|
+
"Think of yourself as a celestial delivery service—bringing joy, hope, and truth straight to their inbox!",
|
20
|
+
"A small effort today could change someone’s eternity. You never know what impact your message will have!",
|
21
|
+
"Faith isn’t about seeing—it’s about acting. Let’s take a leap and send those messages!",
|
22
|
+
"The Lord has prepared these people for you. All that’s left is for you to reach out!",
|
23
|
+
"Heaven is cheering you on, and so am I! Let’s get those unsuccessfully contacted referrals connected!",
|
24
|
+
"Your enthusiasm for the gospel is contagious—now let’s make sure your unsuccessfully contacted referrals catch it!",
|
25
|
+
"Missionary work is about love, and love starts with a simple message. Let’s get to it!",
|
26
|
+
"The Savior knows and loves each of your unsuccessfully contacted referrals—now it’s your turn to help them feel it!",
|
27
|
+
"How many lives can y'all change in one hour?",
|
28
|
+
"You can't say 'hi' to Kolob, but you can to your unsuccessfully contacted referrals!",
|
29
|
+
"Let's have the SuperFaith to contact our unsuccessfully contacted referrals!",
|
30
|
+
"How can we show our unsuccessfully contacted referrals we're excited to meet them?",
|
31
|
+
"Q: WWJDIHHAP?\nA: He would contact his unsuccessfully contacted referrals! ;)",
|
32
|
+
"How long would you want Christ to wait for you?",
|
33
|
+
"Good Morning, y'all! I hope everyone has a great day! Let's BELIEVE, and GO contact those referrals! \n ._.)/\\(._."
|
34
|
+
]
|
35
|
+
}
|
36
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
import { readFileSync } from "fs";
|
2
|
+
import { randomMessage } from "./randomMessage.js";
|
3
|
+
|
4
|
+
export async function prettyString(name, wholeShebang, avg) {
|
5
|
+
let fancifulString = '';
|
6
|
+
const kindMessage = await randomMessage();
|
7
|
+
fancifulString += `${kindMessage}\n\n`;
|
8
|
+
fancifulString += `${avg}\n-->Uncontacted Referrals<--\n\n`;
|
9
|
+
fancifulString += `${name}\n`
|
10
|
+
|
11
|
+
if (Object.keys(wholeShebang).length <= 0) {
|
12
|
+
const options = await JSON.parse(readFileSync('connectToFacebook/crazyJob.json'))
|
13
|
+
const randomIndex = Math.floor(Math.random() * options.length)
|
14
|
+
fancifulString += options[randomIndex]
|
15
|
+
}
|
16
|
+
|
17
|
+
for (const [area, people] of Object.entries(wholeShebang)) {
|
18
|
+
fancifulString += `\n⇒ ${area}\n`
|
19
|
+
for (const person of people.people) {
|
20
|
+
fancifulString += ` ${person}\n`
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
return fancifulString; // Return the constructed string
|
25
|
+
}
|
@@ -0,0 +1,8 @@
|
|
1
|
+
import { readFileSync } from "fs";
|
2
|
+
|
3
|
+
export async function randomMessage() {
|
4
|
+
const options = await JSON.parse(readFileSync('./connectToFacebook/kindMessages.json')).messages
|
5
|
+
const randomIndex = Math.floor(Math.random() * options.length)
|
6
|
+
|
7
|
+
return options[randomIndex]
|
8
|
+
}
|