lazy-apply 0.1.0 → 0.1.1
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 +51 -15
- 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.1" : "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
|
@@ -380,6 +380,48 @@ async function findEmptyRequiredLive(page) {
|
|
|
380
380
|
return page.evaluate(findEmptyRequired);
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
+
// src/lib/apply/autocomplete.ts
|
|
384
|
+
async function pickSuggestion(page, typed, patience = 1300) {
|
|
385
|
+
const items = page.locator('[role="option"], [role="listbox"] li, [class*="dropdown-location"], [class*="dropdown-results"] > *, .pac-item, [class*="suggestion"] li, [class*="autocomplete"] li, [class*="autocomplete"] [class*="item"]').filter({ visible: true });
|
|
386
|
+
const until = Date.now() + patience;
|
|
387
|
+
await page.waitForTimeout(Math.min(700, patience));
|
|
388
|
+
while (!await items.count() && Date.now() < until) await page.waitForTimeout(250);
|
|
389
|
+
const n = await items.count();
|
|
390
|
+
if (!n) return void 0;
|
|
391
|
+
const texts = (await items.allInnerTexts()).map((t) => t.replace(/\s+/g, " ").trim());
|
|
392
|
+
const want = typed.toLowerCase();
|
|
393
|
+
let i = texts.findIndex((t) => t.toLowerCase().startsWith(want));
|
|
394
|
+
if (i < 0) i = texts.findIndex((t) => t.toLowerCase().includes(want));
|
|
395
|
+
if (i < 0) i = 0;
|
|
396
|
+
await items.nth(i).click();
|
|
397
|
+
await page.waitForTimeout(500);
|
|
398
|
+
return texts[i];
|
|
399
|
+
}
|
|
400
|
+
async function leverLocationPicked(page) {
|
|
401
|
+
const hidden = page.locator("#selected-location");
|
|
402
|
+
if (!await hidden.count()) return true;
|
|
403
|
+
return !!(await hidden.inputValue().catch(() => "")).trim();
|
|
404
|
+
}
|
|
405
|
+
async function ensureLeverLocation(page, city) {
|
|
406
|
+
const input = page.locator("#location-input");
|
|
407
|
+
if (!await input.count() || !city) return void 0;
|
|
408
|
+
for (const how of ["click", "keyboard"]) {
|
|
409
|
+
await input.scrollIntoViewIfNeeded();
|
|
410
|
+
await input.fill("");
|
|
411
|
+
await input.pressSequentially(city, { delay: 45 + Math.random() * 40 });
|
|
412
|
+
if (how === "click") await pickSuggestion(page, city, 8e3);
|
|
413
|
+
else {
|
|
414
|
+
await page.locator(".dropdown-location").first().waitFor({ state: "visible", timeout: 8e3 }).catch(() => {
|
|
415
|
+
});
|
|
416
|
+
await input.press("ArrowDown");
|
|
417
|
+
await input.press("Enter");
|
|
418
|
+
await page.waitForTimeout(500);
|
|
419
|
+
}
|
|
420
|
+
if (await leverLocationPicked(page)) return input.inputValue();
|
|
421
|
+
}
|
|
422
|
+
return void 0;
|
|
423
|
+
}
|
|
424
|
+
|
|
383
425
|
// scripts/runner.ts
|
|
384
426
|
import { existsSync, readFileSync as readSync, unlinkSync } from "node:fs";
|
|
385
427
|
var SETTINGS;
|
|
@@ -886,20 +928,6 @@ async function attachResumeGeneric(page, resumePath, notes) {
|
|
|
886
928
|
}
|
|
887
929
|
notes.push(`Resume upload could not be confirmed on the page (looked for "${fileName}")`);
|
|
888
930
|
}
|
|
889
|
-
async function pickSuggestion(page, typed) {
|
|
890
|
-
await page.waitForTimeout(1300);
|
|
891
|
-
const items = page.locator('[role="option"], [role="listbox"] li, [class*="dropdown-location"], [class*="dropdown-results"] > *, .pac-item, [class*="suggestion"] li, [class*="autocomplete"] li, [class*="autocomplete"] [class*="item"]').filter({ visible: true });
|
|
892
|
-
const n = await items.count();
|
|
893
|
-
if (!n) return void 0;
|
|
894
|
-
const texts = (await items.allInnerTexts()).map((t) => t.replace(/\s+/g, " ").trim());
|
|
895
|
-
const want = typed.toLowerCase();
|
|
896
|
-
let i = texts.findIndex((t) => t.toLowerCase().startsWith(want));
|
|
897
|
-
if (i < 0) i = texts.findIndex((t) => t.toLowerCase().includes(want));
|
|
898
|
-
if (i < 0) i = 0;
|
|
899
|
-
await items.nth(i).click();
|
|
900
|
-
await page.waitForTimeout(500);
|
|
901
|
-
return texts[i];
|
|
902
|
-
}
|
|
903
931
|
async function discoverAndFillGeneric(page, app, notes) {
|
|
904
932
|
await page.waitForLoadState("domcontentloaded");
|
|
905
933
|
await page.waitForLoadState("networkidle", { timeout: 8e3 }).catch(() => {
|
|
@@ -961,9 +989,13 @@ async function discoverAndFillGeneric(page, app, notes) {
|
|
|
961
989
|
const isPlace = /location|city|where (are|do) you/i.test(f.label);
|
|
962
990
|
const typed = isPlace ? value.split(",")[0].trim() : value;
|
|
963
991
|
await el.pressSequentially(typed, { delay: jitter(35, 80) });
|
|
964
|
-
const picked = await pickSuggestion(page, typed);
|
|
992
|
+
const picked = await pickSuggestion(page, typed, isPlace ? 8e3 : 1300);
|
|
965
993
|
if (picked) notes.push(`${f.label}: chose "${picked}" from the suggestions`);
|
|
966
994
|
else if (isPlace && await el.inputValue() !== typed) notes.push(`${f.label}: the field did not keep "${typed}"; check it in the window`);
|
|
995
|
+
if (isPlace && !await leverLocationPicked(page)) {
|
|
996
|
+
const fixed = await ensureLeverLocation(page, typed);
|
|
997
|
+
notes.push(fixed ? `${f.label}: picked "${fixed}" on a second try` : `${f.label}: Lever did not accept a location; pick one in the window`);
|
|
998
|
+
}
|
|
967
999
|
}
|
|
968
1000
|
} catch (e) {
|
|
969
1001
|
notes.push(`Could not fill "${f.label}": ${e.message.slice(0, 80)}`);
|
|
@@ -1028,6 +1060,10 @@ async function submit(app) {
|
|
|
1028
1060
|
if (!entry) return;
|
|
1029
1061
|
}
|
|
1030
1062
|
try {
|
|
1063
|
+
if (!await leverLocationPicked(entry.page) && SETTINGS.location) {
|
|
1064
|
+
const fixed = await ensureLeverLocation(entry.page, SETTINGS.location.split(",")[0].trim());
|
|
1065
|
+
log(fixed ? `repaired location for ${app.id}: "${fixed}"` : `could not repair location for ${app.id}`);
|
|
1066
|
+
}
|
|
1031
1067
|
const missing = await findEmptyRequiredLive(entry.page).catch(() => []);
|
|
1032
1068
|
if (missing.length) {
|
|
1033
1069
|
const known = new Set(app.questions.map((q) => q.label.toLowerCase()));
|
package/package.json
CHANGED