@epsilon-asi/actors 0.0.7 → 0.0.10

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.
@@ -0,0 +1,190 @@
1
+ import { z } from "zod/v4";
2
+ /* -------------------------------------------------------------------------------------------------
3
+ * Zod Schemas
4
+ * ------------------------------------------------------------------------------------------------- */
5
+ export var JobCategoryType;
6
+ (function (JobCategoryType) {
7
+ JobCategoryType["hourly"] = "hourly";
8
+ JobCategoryType["fixed"] = "fixed";
9
+ })(JobCategoryType || (JobCategoryType = {}));
10
+ export const UpworkJobListingSchema = z.object({
11
+ id: z.string(),
12
+ title: z.string(),
13
+ url: z.string(),
14
+ postedAtText: z.string().nullable(),
15
+ proposalsText: z.string().nullable(),
16
+ description: z.string(),
17
+ skills: z.array(z.string()),
18
+ category: z.object({
19
+ type: z.enum(JobCategoryType, {}).nullable(),
20
+ raw: z.string().nullable(),
21
+ }),
22
+ hourlyRate: z
23
+ .object({
24
+ min: z.number().nullable(),
25
+ max: z.number().nullable(),
26
+ raw: z.string().nullable(),
27
+ })
28
+ .nullable(),
29
+ fixedBudget: z
30
+ .object({
31
+ amount: z.number().nullable(),
32
+ raw: z.string().nullable(),
33
+ })
34
+ .nullable(),
35
+ experienceLevel: z.string().nullable(),
36
+ estimatedTime: z.string().nullable(),
37
+ client: z.object({
38
+ paymentVerified: z.boolean(),
39
+ rating: z.number().nullable(),
40
+ totalSpent: z.string().nullable(),
41
+ location: z.string().nullable(),
42
+ }),
43
+ rawText: z.string(),
44
+ });
45
+ /* -------------------------------------------------------------------------------------------------
46
+ * Helpers
47
+ * ------------------------------------------------------------------------------------------------- */
48
+ function cleanText(input) {
49
+ return (input ?? "")
50
+ .replace(/\s+/g, " ")
51
+ .replace(/\u00a0/g, " ")
52
+ .trim();
53
+ }
54
+ function parseMoney(value) {
55
+ if (!value)
56
+ return null;
57
+ const normalized = value.replace(/[^0-9.]/g, "");
58
+ if (!normalized) {
59
+ return null;
60
+ }
61
+ const parsed = Number(normalized);
62
+ return Number.isNaN(parsed) ? null : parsed;
63
+ }
64
+ function parseHourlyRate(raw) {
65
+ if (!raw) {
66
+ return null;
67
+ }
68
+ const matches = raw.match(/\$([\d,.]+)\s*-\s*\$([\d,.]+)/);
69
+ if (!matches || matches.length < 3) {
70
+ return {
71
+ min: null,
72
+ max: null,
73
+ raw,
74
+ };
75
+ }
76
+ return {
77
+ min: parseMoney(matches[1]),
78
+ max: parseMoney(matches[2]),
79
+ raw,
80
+ };
81
+ }
82
+ function parseFixedBudget(raw) {
83
+ if (!raw) {
84
+ return null;
85
+ }
86
+ const match = raw.match(/\$([\d,.]+)/);
87
+ return {
88
+ amount: match ? parseMoney(match[1]) : null,
89
+ raw,
90
+ };
91
+ }
92
+ /* -------------------------------------------------------------------------------------------------
93
+ * Core Parser
94
+ * ------------------------------------------------------------------------------------------------- */
95
+ export async function parseUpworkJobListing(article) {
96
+ const extracted = await article.evaluate((node) => {
97
+ const getText = (selector) => {
98
+ const element = node.querySelector(selector);
99
+ return element?.textContent?.trim() ?? null;
100
+ };
101
+ const getTexts = (selector) => {
102
+ return Array.from(node.querySelectorAll(selector))
103
+ .map((el) => el.textContent?.trim() ?? "")
104
+ .filter(Boolean);
105
+ };
106
+ const titleAnchor = node.querySelector('[data-test="job-tile-title-link UpLink"]');
107
+ const jobInfoItems = Array.from(node.querySelectorAll('[data-test="JobInfo"] li')).map((li) => li.textContent?.trim() ?? "");
108
+ const clientInfoItems = Array.from(node.querySelectorAll('[data-test="JobInfoClient"] li')).map((li) => li.textContent?.trim() ?? "");
109
+ const hourlyItem = jobInfoItems.find((x) => x.toLowerCase().includes("hourly")) ?? null;
110
+ const fixedItem = jobInfoItems.find((x) => x.toLowerCase().includes("fixed")) ?? null;
111
+ const experienceLevel = node
112
+ .querySelector('[data-test="experience-level"]')
113
+ ?.textContent?.trim() ?? null;
114
+ const estimatedTime = node
115
+ .querySelector('[data-test="duration-label"]')
116
+ ?.textContent?.trim() ?? null;
117
+ const ratingText = node
118
+ .querySelector('[data-test="feedback-rating UpCRating"]')
119
+ ?.textContent?.trim() ?? null;
120
+ const spentText = node
121
+ .querySelector('[data-test="total-spent"]')
122
+ ?.textContent?.trim() ?? null;
123
+ const locationText = node
124
+ .querySelector('[data-test="location"]')
125
+ ?.textContent?.trim() ?? null;
126
+ return {
127
+ id: node.getAttribute("data-ev-job-uid") ??
128
+ node.getAttribute("data-test-key") ??
129
+ "",
130
+ title: titleAnchor?.textContent?.trim() ?? "",
131
+ url: titleAnchor?.href ?? "",
132
+ postedAtText: getText('[data-test="job-pubilshed-date"] span:first-child') ?? null,
133
+ proposalsText: getText('[data-test="proposals-tier"]') ?? null,
134
+ description: getText('[data-test="UpCLineClamp JobDescription"] p') ?? "",
135
+ skills: getTexts('[data-test="token"] span'),
136
+ hourlyRaw: hourlyItem,
137
+ fixedRaw: fixedItem,
138
+ experienceLevel,
139
+ estimatedTime,
140
+ paymentVerified: node.querySelector('[data-test="payment-verified"]') !== null,
141
+ ratingText,
142
+ spentText,
143
+ locationText,
144
+ rawText: node.textContent ?? "",
145
+ categoryType: hourlyItem
146
+ ? "hourly"
147
+ : fixedItem
148
+ ? "fixed"
149
+ : null,
150
+ };
151
+ });
152
+ const hourlyRate = parseHourlyRate(extracted.hourlyRaw);
153
+ const fixedBudget = parseFixedBudget(extracted.fixedRaw);
154
+ const ratingMatch = extracted.ratingText?.match(/(\d+(\.\d+)?)/);
155
+ const parsed = {
156
+ id: cleanText(extracted.id),
157
+ title: cleanText(extracted.title),
158
+ url: cleanText(extracted.url),
159
+ postedAtText: cleanText(extracted.postedAtText),
160
+ proposalsText: cleanText(extracted.proposalsText),
161
+ description: cleanText(extracted.description),
162
+ skills: extracted.skills.map(cleanText),
163
+ category: {
164
+ type: extracted.categoryType,
165
+ raw: cleanText(extracted.hourlyRaw ?? extracted.fixedRaw),
166
+ },
167
+ hourlyRate,
168
+ fixedBudget,
169
+ experienceLevel: cleanText(extracted.experienceLevel),
170
+ estimatedTime: cleanText(extracted.estimatedTime),
171
+ client: {
172
+ paymentVerified: extracted.paymentVerified,
173
+ rating: ratingMatch ? Number(ratingMatch[1]) : null,
174
+ totalSpent: cleanText(extracted.spentText),
175
+ location: cleanText(extracted.locationText),
176
+ },
177
+ rawText: cleanText(extracted.rawText),
178
+ };
179
+ return UpworkJobListingSchema.parse(parsed);
180
+ }
181
+ /* -------------------------------------------------------------------------------------------------
182
+ * Multi-Listing Parser
183
+ * ------------------------------------------------------------------------------------------------- */
184
+ export async function parseUpworkSearchResults(page) {
185
+ await page.waitForSelector('#main > section > article[data-ev-sublocation="search_results"]');
186
+ const articles = await page.$$('#main > section > article[data-ev-sublocation="search_results"]');
187
+ const results = await Promise.all(articles.map((article) => parseUpworkJobListing(article)));
188
+ return results;
189
+ }
190
+ //# sourceMappingURL=scrapeJobListing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scrapeJobListing.js","sourceRoot":"","sources":["../../../../src/sites/upwork-com/util/scrapeJobListing.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAC,MAAM,QAAQ,CAAC;AAEzB;;uGAEuG;AACvG,MAAM,CAAN,IAAY,eAGX;AAHD,WAAY,eAAe;IACvB,oCAAmB,CAAA;IACnB,kCAAiB,CAAA;AACrB,CAAC,EAHW,eAAe,KAAf,eAAe,QAG1B;AAED,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC3C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IAEd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IAEf,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACnC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEpC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IAEvB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IAE3B,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE;QAC5C,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC;IAEF,UAAU,EAAE,CAAC;SACR,MAAM,CAAC;QACJ,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC1B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC1B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC;SACD,QAAQ,EAAE;IAEf,WAAW,EAAE,CAAC;SACT,MAAM,CAAC;QACJ,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC7B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC;SACD,QAAQ,EAAE;IAEf,eAAe,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEtC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAEpC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACb,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE;QAE5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAE7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAEjC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAClC,CAAC;IAEF,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;CACtB,CAAC,CAAC;AAIH;;uGAEuG;AAEvG,SAAS,SAAS,CAAC,KAAqB;IACpC,OAAO,CAAC,KAAK,IAAI,EAAE,CAAC;SACf,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,SAAS,EAAE,GAAG,CAAC;SACvB,IAAI,EAAE,CAAC;AAChB,CAAC;AAED,SAAS,UAAU,CAAC,KAAc;IAC9B,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,UAAU,GAAG,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC;IAEjD,IAAI,CAAC,UAAU,EAAE,CAAC;QACd,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC;IAElC,OAAO,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AAChD,CAAC;AAED,SAAS,eAAe,CACpB,GAAkB;IAElB,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;IAE3D,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,OAAO;YACH,GAAG,EAAE,IAAI;YACT,GAAG,EAAE,IAAI;YACT,GAAG;SACN,CAAC;IACN,CAAC;IACD,OAAO;QACH,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3B,GAAG,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3B,GAAG;KACN,CAAC;AACN,CAAC;AAED,SAAS,gBAAgB,CACrB,GAAkB;IAElB,IAAI,CAAC,GAAG,EAAE,CAAC;QACP,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAEvC,OAAO;QACH,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QAC3C,GAAG;KACN,CAAC;AACN,CAAC;AAED;;uGAEuG;AAEvG,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACvC,OAA+B;IAE/B,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE;QAC9C,MAAM,OAAO,GAAG,CAAC,QAAgB,EAAiB,EAAE;YAChD,MAAM,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAE7C,OAAO,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAChD,CAAC,CAAC;QAEF,MAAM,QAAQ,GAAG,CAAC,QAAgB,EAAY,EAAE;YAC5C,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;iBAC7C,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;iBACzC,MAAM,CAAC,OAAO,CAAC,CAAC;QACzB,CAAC,CAAC;QAEF,MAAM,WAAW,GAAG,IAAI,CAAC,aAAa,CAClC,0CAA0C,CACjB,CAAC;QAE9B,MAAM,YAAY,GAAG,KAAK,CAAC,IAAI,CAC3B,IAAI,CAAC,gBAAgB,CAAC,0BAA0B,CAAC,CACpD,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,MAAM,eAAe,GAAG,KAAK,CAAC,IAAI,CAC9B,IAAI,CAAC,gBAAgB,CAAC,gCAAgC,CAAC,CAC1D,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,MAAM,UAAU,GACZ,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,IAAI,IAAI,CAAC;QAEzE,MAAM,SAAS,GACX,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC;QAExE,MAAM,eAAe,GACjB,IAAI;aACC,aAAa,CAAC,gCAAgC,CAAC;YAChD,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAEtC,MAAM,aAAa,GACf,IAAI;aACC,aAAa,CAAC,8BAA8B,CAAC;YAC9C,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAEtC,MAAM,UAAU,GACZ,IAAI;aACC,aAAa,CAAC,yCAAyC,CAAC;YACzD,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAEtC,MAAM,SAAS,GACX,IAAI;aACC,aAAa,CAAC,2BAA2B,CAAC;YAC3C,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAEtC,MAAM,YAAY,GACd,IAAI;aACC,aAAa,CAAC,wBAAwB,CAAC;YACxC,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC;QAEtC,OAAO;YACH,EAAE,EACE,IAAI,CAAC,YAAY,CAAC,iBAAiB,CAAC;gBACpC,IAAI,CAAC,YAAY,CAAC,eAAe,CAAC;gBAClC,EAAE;YAEN,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,EAAE;YAE7C,GAAG,EAAE,WAAW,EAAE,IAAI,IAAI,EAAE;YAE5B,YAAY,EACR,OAAO,CAAC,mDAAmD,CAAC,IAAI,IAAI;YAExE,aAAa,EACT,OAAO,CAAC,8BAA8B,CAAC,IAAI,IAAI;YAEnD,WAAW,EACP,OAAO,CAAC,6CAA6C,CAAC,IAAI,EAAE;YAEhE,MAAM,EAAE,QAAQ,CAAC,0BAA0B,CAAC;YAE5C,SAAS,EAAE,UAAU;YACrB,QAAQ,EAAE,SAAS;YAEnB,eAAe;YACf,aAAa;YAEb,eAAe,EACX,IAAI,CAAC,aAAa,CAAC,gCAAgC,CAAC,KAAK,IAAI;YAEjE,UAAU;YACV,SAAS;YACT,YAAY;YAEZ,OAAO,EAAE,IAAI,CAAC,WAAW,IAAI,EAAE;YAE/B,YAAY,EAAE,UAAU;gBACpB,CAAC,CAAC,QAAQ;gBACV,CAAC,CAAC,SAAS;oBACP,CAAC,CAAC,OAAO;oBACT,CAAC,CAAC,IAAI;SACjB,CAAC;IACN,CAAC,CAAC,CAAC;IAEH,MAAM,UAAU,GAAG,eAAe,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACxD,MAAM,WAAW,GAAG,gBAAgB,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IAEzD,MAAM,WAAW,GAAG,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,eAAe,CAAC,CAAC;IAEjE,MAAM,MAAM,GAAqB;QAC7B,EAAE,EAAE,SAAS,CAAC,SAAS,CAAC,EAAE,CAAC;QAE3B,KAAK,EAAE,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC;QAEjC,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,GAAG,CAAC;QAE7B,YAAY,EAAE,SAAS,CAAC,SAAS,CAAC,YAAY,CAAC;QAE/C,aAAa,EAAE,SAAS,CAAC,SAAS,CAAC,aAAa,CAAC;QAEjD,WAAW,EAAE,SAAS,CAAC,SAAS,CAAC,WAAW,CAAC;QAE7C,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC;QAEvC,QAAQ,EAAE;YACN,IAAI,EAAE,SAAS,CAAC,YAAwC;YACxD,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC;SAC5D;QAED,UAAU;QAEV,WAAW;QAEX,eAAe,EAAE,SAAS,CAAC,SAAS,CAAC,eAAe,CAAC;QAErD,aAAa,EAAE,SAAS,CAAC,SAAS,CAAC,aAAa,CAAC;QAEjD,MAAM,EAAE;YACJ,eAAe,EAAE,SAAS,CAAC,eAAe;YAE1C,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;YAEnD,UAAU,EAAE,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC;YAE1C,QAAQ,EAAE,SAAS,CAAC,SAAS,CAAC,YAAY,CAAC;SAC9C;QAED,OAAO,EAAE,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC;KACxC,CAAC;IAEF,OAAO,sBAAsB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED;;uGAEuG;AAEvG,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC1C,IAAU;IAEV,MAAM,IAAI,CAAC,eAAe,CAAC,iEAAiE,CAAC,CAAC;IAE9F,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,EAAE,CAC1B,iEAAiE,CACpE,CAAC;IAEF,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC7B,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC,CAC5D,CAAC;IAEF,OAAO,OAAO,CAAC;AACnB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epsilon-asi/actors",
3
- "version": "0.0.7",
3
+ "version": "0.0.10",
4
4
  "description": "A TypeScript Puppeteer actor framework using existing Chrome profiles and ghost-cursor.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -18,19 +18,23 @@
18
18
  "scripts": {
19
19
  "build": "tsc -p tsconfig.build.json",
20
20
  "typecheck": "tsc --noEmit -p tsconfig.json",
21
+ "cli": "npx tsx ./src/cli/run.ts",
21
22
  "test": "vitest run",
22
23
  "test:coverage": "vitest run --coverage",
23
24
  "check": "npm run typecheck && npm test",
25
+ "bump": "../../scripts/bump_version.sh",
26
+ "publish": "npm run bump && npm publish --access public",
24
27
  "clean": "rm -rf dist coverage"
25
28
  },
26
29
  "dependencies": {
30
+ "cheerio": "^1.2.0",
27
31
  "ghost-cursor": "^1.4.2",
28
32
  "puppeteer-core": "24.43.1"
29
33
  },
30
34
  "devDependencies": {
35
+ "@types/cheerio": "^0.22.35",
31
36
  "@types/node": "^24.10.1",
32
37
  "@vitest/coverage-v8": "^4.1.7",
33
-
34
38
  "vitest": "^4.1.7"
35
39
  },
36
40
  "engines": {
@@ -4,6 +4,8 @@ import type { HumanTypingOptions } from '../interaction/HumanTyping.js';
4
4
 
5
5
  export type ChromeReleaseChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary';
6
6
 
7
+ const DEFAULT_VIEWPORT = { width: 1080, height: 1920 };
8
+
7
9
  export interface BrowserSessionBehavior {
8
10
  defaultViewport?: { width: number; height: number } | null;
9
11
  startUrl?: string;
@@ -120,6 +122,7 @@ function normalizeExistingProfileConfig(browser: ExistingChromeProfileConfig): E
120
122
  userDataDir: path.resolve(expandHome(browser.userDataDir)),
121
123
  channel: browser.channel ?? 'chrome',
122
124
  headless: false,
125
+ defaultViewport: browser.defaultViewport === undefined ? DEFAULT_VIEWPORT : browser.defaultViewport,
123
126
  reuseExistingPage: browser.reuseExistingPage ?? true,
124
127
  closeBrowserOnFinish: browser.closeBrowserOnFinish ?? true,
125
128
  allowCreatingUserDataDir: browser.allowCreatingUserDataDir ?? false,
@@ -133,6 +136,7 @@ function normalizeRemoteDebuggingConfig(browser: RemoteDebuggingChromeConfig): R
133
136
  ...browser,
134
137
  remoteDebuggingHost: browser.remoteDebuggingHost ?? '127.0.0.1',
135
138
  remoteDebuggingPort: browser.remoteDebuggingPort ?? 9222,
139
+ defaultViewport: browser.defaultViewport === undefined ? DEFAULT_VIEWPORT : browser.defaultViewport,
136
140
  reuseExistingPage: browser.reuseExistingPage ?? false,
137
141
  closeBrowserOnFinish: browser.closeBrowserOnFinish ?? false,
138
142
  disconnectOnFinish: browser.disconnectOnFinish ?? true
@@ -1,97 +1,90 @@
1
1
  import {defineLoginFlow} from '../../auth/LoginFlow.types.js';
2
2
  import {defineActor} from '../../core/defineActor.js';
3
3
  import {upworkComSelectors} from './upwork-com.selectors.js';
4
- import type {
5
- UpworkApplyToJobInput,
6
- UpworkApplyToJobResult,
7
- UpworkJobSearchFields,
8
- UpworkJobSearchResult
9
- } from './upwork-com.types.js';
10
- import {buildSearchParams, parseRate} from "./upwork-com.util.js";
4
+ import type {UpworkApplyToJobInput, UpworkApplyToJobResult, UpworkJobSearchFields} from './upwork-com.types.js';
5
+ import {parseRate} from "./upwork-com.util.js";
6
+ import {ScrapeDashboardInput} from "../example/index.js";
7
+ import {parseUpworkSearchResults, UpworkJobListing} from "./util/scrapeJobListing.js";
8
+ import {Page} from "puppeteer-core";
11
9
 
12
10
  export const upworkComActor = defineActor({
13
- id: 'upwork-com',
14
- baseUrl: 'https://upwork.com',
15
- auth: defineLoginFlow({
16
- loginUrl: 'https://www.upwork.com/ab/account-security/login',
17
- selectors: {
18
- loggedInSignal: upworkComSelectors.login.loggedInSignal,
19
- errorMessage: upworkComSelectors.login.errorMessage
20
- },
21
- credentials: { id: 'upwork' },
22
- behavior: {
23
- authCheckUrl: '/',
24
- loggedInTimeoutMs: 5_000,
25
- errorTimeoutMs: 1_500,
26
- typing: {
27
- targetWordsPerMinute: 65,
28
- intervalJitterMs: 18
29
- }
30
- },
31
- steps: [
32
- {
33
- type: 'fill',
34
- name: 'username',
35
- selector: upworkComSelectors.login.username,
36
- credential: 'username'
37
- },
38
- {
39
- type: 'click',
40
- name: 'continue to password',
41
- selector: upworkComSelectors.login.continueToPassword,
42
- waitForSelector: upworkComSelectors.login.password,
43
- waitForSelectorTimeoutMs: 5_000
44
- },
45
- {
46
- type: 'fill',
47
- name: 'password',
48
- selector: upworkComSelectors.login.password,
49
- credential: 'password'
50
- },
51
- {
52
- type: 'click',
53
- name: 'submit password',
54
- selector: upworkComSelectors.login.submit,
55
- submit: true,
56
- waitForNavigation: true,
57
- checkForError: false
58
- }
59
- ]
60
- }),
61
- tasks: {
62
- searchJobs: async (context, input: UpworkJobSearchFields = {}): Promise<any> => {
63
- const path = '/nx/s/universal-search/jobs?client_hires=1-9,10-&payment_verified=1&q=%27rancher%27%20or%20%27terraform%27%20or%20%27gitops%27%20or%20%27azure%27%20or%20%27microsoft%20azure%27%20or%20%27cloud%20architect%27%20or%20%27ai%20architect%27%20or%20%27forward%20deployed%20engineer%27%20or%20%27aws%27%20or%20%27aks%27%20or%20%27eks%27%20or%20%27gke%27%20or%20%27cloud%20engineer%27%20or%20devops%20or%20kuberentes%20or%20%27platform%20engineer%27%20or%20%27infrastructure%20engineer%27%20or%20"google%20cloud%20platform"%20or%20"GCP"%20or%20"langsmith"%20or%20"langgraph"%20or%20"gemini%20enterprise"&sort=recency&user_location_match=1';
11
+ id: 'upwork-com',
12
+ baseUrl: 'https://upwork.com',
13
+ auth: defineLoginFlow({
14
+ loginUrl: 'https://www.upwork.com/ab/account-security/login',
15
+ selectors: {
16
+ loggedInSignal: upworkComSelectors.login.loggedInSignal,
17
+ errorMessage: upworkComSelectors.login.errorMessage
18
+ },
19
+ credentials: {id: 'upwork'},
20
+ behavior: {
21
+ authCheckUrl: '/',
22
+ loggedInTimeoutMs: 5_000,
23
+ errorTimeoutMs: 1_500,
24
+ typing: {
25
+ targetWordsPerMinute: 65,
26
+ intervalJitterMs: 18
27
+ }
28
+ },
29
+ steps: [
30
+ {
31
+ type: 'fill',
32
+ name: 'username',
33
+ selector: upworkComSelectors.login.username,
34
+ credential: 'username'
35
+ },
36
+ {
37
+ type: 'click',
38
+ name: 'continue to password',
39
+ selector: upworkComSelectors.login.continueToPassword,
40
+ waitForSelector: upworkComSelectors.login.password,
41
+ waitForSelectorTimeoutMs: 5_000
42
+ },
43
+ {
44
+ type: 'fill',
45
+ name: 'password',
46
+ selector: upworkComSelectors.login.password,
47
+ credential: 'password'
48
+ },
49
+ {
50
+ type: 'click',
51
+ name: 'submit password',
52
+ selector: upworkComSelectors.login.submit,
53
+ submit: true,
54
+ waitForNavigation: true,
55
+ checkForError: false
56
+ }
57
+ ]
58
+ }),
59
+ tasks: {
60
+ searchJobs: async (context, input: UpworkJobSearchFields = {}): Promise<UpworkJobListing[]> => {
61
+ const path = '/nx/s/universal-search/jobs?category2_uid=531770282580668420,531770282580668419,531770282580668418&client_hires=1-9,10-&payment_verified=1&q=%27rancher%27%20or%20%27terraform%27%20or%20%27gitops%27%20or%20%27azure%27%20or%20%27microsoft%20azure%27%20or%20%27cloud%20architect%27%20or%20%27ai%20architect%27%20or%20%27forward%20deployed%20engineer%27%20or%20%27aws%27%20or%20%27aks%27%20or%20%27eks%27%20or%20%27gke%27%20or%20%27cloud%20engineer%27%20or%20devops%20or%20kuberentes%20or%20%27platform%20engineer%27%20or%20%27infrastructure%20engineer%27%20or%20"google%20cloud%20platform"%20or%20"GCP"%20or%20"langsmith"%20or%20"langgraph"%20or%20"gemini%20enterprise"&sort=recency&user_location_match=1';
64
62
 
65
- await context.nav.goto(path, {});
63
+ await context.nav.goto(path, {
66
64
 
67
- console.log(context.page.raw());
65
+ });
68
66
 
69
- // Get by classname class="jobs-main-panel - get all children of article type
70
- // loop through each. Check
71
- //
72
- //
73
- },
74
- applyToJob: async (_context, input: UpworkApplyToJobInput): Promise<UpworkApplyToJobResult> => {
75
- const coverLetter = input.coverLetter.trim();
76
- if (coverLetter.length === 0) {
77
- throw new Error('coverLetter must be a non-empty string.');
78
- }
67
+ return await parseUpworkSearchResults(context.session.page as Page);
68
+ },
69
+ scrapeJobs: async (context, input: ScrapeDashboardInput = {}): Promise<any> => {
70
+ await parseUpworkSearchResults(context.session.page as Page)
71
+ },
72
+ applyToJob: async (_context, input: UpworkApplyToJobInput): Promise<UpworkApplyToJobResult> => {
73
+ const coverLetter = input.coverLetter.trim();
74
+ if (coverLetter.length === 0) {
75
+ throw new Error('coverLetter must be a non-empty string.');
76
+ }
79
77
 
80
- const rate = parseRate(input.rate);
81
- if (!Number.isFinite(rate) || rate <= 0) {
82
- throw new Error('rate must be a positive number.');
83
- }
78
+ const rate = parseRate(input.rate);
79
+ if (!Number.isFinite(rate) || rate <= 0) {
80
+ throw new Error('rate must be a positive number.');
81
+ }
84
82
 
85
- return {
86
- coverLetter,
87
- rate
88
- };
83
+ return {
84
+ coverLetter,
85
+ rate
86
+ };
87
+ }
89
88
  }
90
- }
91
89
  });
92
-
93
-
94
- const likeScript = `reactions.action_reactions(this, 1632181); `
95
- const commentScript = `activity.comment_save(1630927, this);`
96
-
97
-
90
+ `asd*poq@#aes48d#@jna8sndaskDF#KN$@#a78sdASD1`