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 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.1" : "dev";
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]")?.textContent);
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?.textContent);
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}"]`)?.textContent || "";
314
- if (!label) label = el.closest("label")?.textContent || "";
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?.textContent?.trim()) {
322
- label = lab.textContent;
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.textContent || "";
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.pressSequentially(value, { delay: jitter(35, 80) });
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.pressSequentially(typed, { delay: jitter(35, 80) });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lazy-apply",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "The Lazy Apply runner: fills your approved job applications in Chrome on your own computer. Run `npx lazy-apply`.",
5
5
  "type": "module",
6
6
  "bin": { "lazy-apply": "dist/cli.js" },