lazy-apply 0.1.1 → 0.1.2
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.
- package/dist/cli.js +1 -1
- package/dist/runner.js +56 -10
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node
|
|
|
6
6
|
import { homedir, hostname, platform, tmpdir, userInfo } from "node:os";
|
|
7
7
|
import { dirname, join } from "node:path";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
|
-
var VERSION = true ? "0.1.
|
|
9
|
+
var VERSION = true ? "0.1.2" : "dev";
|
|
10
10
|
var DEFAULT_APP = "https://lazyapply.online";
|
|
11
11
|
var HOME = join(homedir(), ".lazy-apply");
|
|
12
12
|
var CONFIG = join(HOME, "config.json");
|
package/dist/runner.js
CHANGED
|
@@ -254,6 +254,25 @@ function resumeFileName(...nameParts) {
|
|
|
254
254
|
// src/lib/apply/discover.ts
|
|
255
255
|
function discoverFields() {
|
|
256
256
|
const clean = (s) => (s || "").replace(/\s+/g, " ").replace(/[✱*]/g, "").trim();
|
|
257
|
+
const NOISE = "input, select, textarea, button, svg, script, style, [class*=dropdown], [role=listbox], [role=option], [aria-hidden=true]";
|
|
258
|
+
const textOf = (el) => {
|
|
259
|
+
if (!el) return "";
|
|
260
|
+
let out2 = "";
|
|
261
|
+
const walk = (n) => {
|
|
262
|
+
for (const c of Array.from(n.childNodes)) {
|
|
263
|
+
if (c.nodeType === 3) out2 += c.textContent;
|
|
264
|
+
else if (c.nodeType === 1) {
|
|
265
|
+
const e = c;
|
|
266
|
+
if (e.matches(NOISE)) continue;
|
|
267
|
+
const cs = getComputedStyle(e);
|
|
268
|
+
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
|
269
|
+
walk(e);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
walk(el);
|
|
274
|
+
return out2;
|
|
275
|
+
};
|
|
257
276
|
const optionLabel = (el) => clean(el.id && document.querySelector(`label[for="${el.id}"]`)?.textContent || el.closest("label")?.textContent);
|
|
258
277
|
const out = [];
|
|
259
278
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -269,7 +288,7 @@ function discoverFields() {
|
|
|
269
288
|
let gi = 0;
|
|
270
289
|
for (const [box, buttons] of buttonGroups) {
|
|
271
290
|
if (buttons.length < 2 || buttons.length > 8) continue;
|
|
272
|
-
const label = clean(box.querySelector("label, legend, [class*=question-title], [class*=title], [class*=label]")
|
|
291
|
+
const label = clean(textOf(box.querySelector("label, legend, [class*=question-title], [class*=title], [class*=label]")));
|
|
273
292
|
if (!label) continue;
|
|
274
293
|
const key = `aa-btn-${gi++}`;
|
|
275
294
|
box.setAttribute("data-aa-group", key);
|
|
@@ -289,7 +308,7 @@ function discoverFields() {
|
|
|
289
308
|
const controls = Array.from(node.querySelectorAll("input, textarea, select")).filter((i) => !["hidden", "submit", "button"].includes(i.type));
|
|
290
309
|
if (!controls.every((i) => (i.type === "radio" || i.type === "checkbox") && i.name === input.name)) break;
|
|
291
310
|
const cand = node.querySelector("legend, h3, h4, h5, h6, [class*=question], [class*=Label], [class*=label]:not(label), .application-label");
|
|
292
|
-
const t = clean(cand
|
|
311
|
+
const t = clean(textOf(cand));
|
|
293
312
|
if (t && t !== opt) question = t;
|
|
294
313
|
node = node.parentElement;
|
|
295
314
|
}
|
|
@@ -310,16 +329,16 @@ function discoverFields() {
|
|
|
310
329
|
if (seen.has(selector)) return;
|
|
311
330
|
seen.add(selector);
|
|
312
331
|
let label = "";
|
|
313
|
-
if (el.id) label = document.querySelector(`label[for="${el.id}"]`)
|
|
314
|
-
if (!label) label = el.closest("label")
|
|
332
|
+
if (el.id) label = textOf(document.querySelector(`label[for="${el.id}"]`));
|
|
333
|
+
if (!label.trim()) label = textOf(el.closest("label"));
|
|
315
334
|
if (!label) {
|
|
316
335
|
let node = el.parentElement;
|
|
317
336
|
while (node && node !== document.body) {
|
|
318
337
|
const controls = node.querySelectorAll("input:not([type=hidden]):not([type=submit]):not([type=button]), textarea, select").length;
|
|
319
338
|
if (controls > 1) break;
|
|
320
339
|
const lab = node.querySelector(".application-label, label, legend, [class*=question-title], [class*=label]:not(input)");
|
|
321
|
-
if (lab
|
|
322
|
-
label = lab
|
|
340
|
+
if (textOf(lab).trim()) {
|
|
341
|
+
label = textOf(lab);
|
|
323
342
|
break;
|
|
324
343
|
}
|
|
325
344
|
node = node.parentElement;
|
|
@@ -349,11 +368,30 @@ async function discoverLiveFields(page) {
|
|
|
349
368
|
}
|
|
350
369
|
function findEmptyRequired() {
|
|
351
370
|
const clean = (s) => (s || "").replace(/\s+/g, " ").replace(/[✱*]/g, "").trim();
|
|
371
|
+
const NOISE = "input, select, textarea, button, svg, script, style, [class*=dropdown], [role=listbox], [role=option], [aria-hidden=true]";
|
|
372
|
+
const textOf = (el) => {
|
|
373
|
+
if (!el) return "";
|
|
374
|
+
let out2 = "";
|
|
375
|
+
const walk = (n) => {
|
|
376
|
+
for (const c of Array.from(n.childNodes)) {
|
|
377
|
+
if (c.nodeType === 3) out2 += c.textContent;
|
|
378
|
+
else if (c.nodeType === 1) {
|
|
379
|
+
const e = c;
|
|
380
|
+
if (e.matches(NOISE)) continue;
|
|
381
|
+
const cs = getComputedStyle(e);
|
|
382
|
+
if (cs.display === "none" || cs.visibility === "hidden") continue;
|
|
383
|
+
walk(e);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
walk(el);
|
|
388
|
+
return out2;
|
|
389
|
+
};
|
|
352
390
|
const seen = /* @__PURE__ */ new Set();
|
|
353
391
|
const out = [];
|
|
354
392
|
const labels = Array.from(document.querySelectorAll("label, legend, [class*=question-title]"));
|
|
355
393
|
for (const lab of labels) {
|
|
356
|
-
const raw = lab
|
|
394
|
+
const raw = textOf(lab);
|
|
357
395
|
const ctl = lab.control;
|
|
358
396
|
const box = lab.closest("[data-field-entry-id], fieldset, [class*=field-entry], [class*=question], [class*=field], li") || lab.parentElement;
|
|
359
397
|
if (!box) continue;
|
|
@@ -449,6 +487,14 @@ var hostBackoffUntil = /* @__PURE__ */ new Map();
|
|
|
449
487
|
var SUBMIT_GAP_MS = 9e4;
|
|
450
488
|
var SPAM_BACKOFF_MS = 6 * 6e4;
|
|
451
489
|
var jitter = (min, max) => min + Math.random() * (max - min);
|
|
490
|
+
async function typeAnswer(el, value) {
|
|
491
|
+
if (value.length <= 120) {
|
|
492
|
+
await el.pressSequentially(value, { delay: jitter(35, 80), timeout: 0 });
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
await el.fill(value.slice(0, -1));
|
|
496
|
+
await el.pressSequentially(value.slice(-1), { delay: 40 });
|
|
497
|
+
}
|
|
452
498
|
var LOCK = join(tmpdir(), `auto-apply-runner-${PROFILE_DIR}-${APP_URL.replace(/[^a-z0-9]+/gi, "_")}.lock`);
|
|
453
499
|
if (existsSync(LOCK)) {
|
|
454
500
|
const pid = Number(readSync(LOCK, "utf8"));
|
|
@@ -535,7 +581,7 @@ async function fillGreenhouse(page, app, notes) {
|
|
|
535
581
|
await el.scrollIntoViewIfNeeded();
|
|
536
582
|
await el.click();
|
|
537
583
|
await el.fill("");
|
|
538
|
-
await el
|
|
584
|
+
await typeAnswer(el, value);
|
|
539
585
|
return true;
|
|
540
586
|
};
|
|
541
587
|
const norm = (t) => t.replace(/[\u2018\u2019]/g, "'").replace(/[\u201c\u201d]/g, '"').replace(/\s+/g, " ").trim().toLowerCase();
|
|
@@ -988,7 +1034,7 @@ async function discoverAndFillGeneric(page, app, notes) {
|
|
|
988
1034
|
await el.fill("");
|
|
989
1035
|
const isPlace = /location|city|where (are|do) you/i.test(f.label);
|
|
990
1036
|
const typed = isPlace ? value.split(",")[0].trim() : value;
|
|
991
|
-
await el
|
|
1037
|
+
await typeAnswer(el, typed);
|
|
992
1038
|
const picked = await pickSuggestion(page, typed, isPlace ? 8e3 : 1300);
|
|
993
1039
|
if (picked) notes.push(`${f.label}: chose "${picked}" from the suggestions`);
|
|
994
1040
|
else if (isPlace && await el.inputValue() !== typed) notes.push(`${f.label}: the field did not keep "${typed}"; check it in the window`);
|
|
@@ -1003,7 +1049,7 @@ async function discoverAndFillGeneric(page, app, notes) {
|
|
|
1003
1049
|
}
|
|
1004
1050
|
await fillAshbyEducation(page, notes);
|
|
1005
1051
|
const eduLabel = /search schools|^degree$|field of study|still (a )?student/i;
|
|
1006
|
-
const stillOpen = unanswered.filter((u) => !eduLabel.test(u.label));
|
|
1052
|
+
const stillOpen = unanswered.filter((u) => !eduLabel.test(u.label) && u.required);
|
|
1007
1053
|
if (stillOpen.length) {
|
|
1008
1054
|
notes.push(`${stillOpen.length} field(s) need your answer: ${stillOpen.map((u) => u.label).join("; ")}`);
|
|
1009
1055
|
await report(app.id, { questions: stillOpen });
|
package/package.json
CHANGED