@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.
- package/dist/browser/RuntimeConfig.d.ts.map +1 -1
- package/dist/browser/RuntimeConfig.js +3 -0
- package/dist/browser/RuntimeConfig.js.map +1 -1
- package/dist/sites/upwork-com/upwork-com.actor.d.ts +4 -1
- package/dist/sites/upwork-com/upwork-com.actor.d.ts.map +1 -1
- package/dist/sites/upwork-com/upwork-com.actor.js +7 -8
- package/dist/sites/upwork-com/upwork-com.actor.js.map +1 -1
- package/dist/sites/upwork-com/util/parseJobDetails.d.ts +105 -0
- package/dist/sites/upwork-com/util/parseJobDetails.d.ts.map +1 -0
- package/dist/sites/upwork-com/util/parseJobDetails.js +335 -0
- package/dist/sites/upwork-com/util/parseJobDetails.js.map +1 -0
- package/dist/sites/upwork-com/util/scrapeJobListing.d.ts +41 -0
- package/dist/sites/upwork-com/util/scrapeJobListing.d.ts.map +1 -0
- package/dist/sites/upwork-com/util/scrapeJobListing.js +190 -0
- package/dist/sites/upwork-com/util/scrapeJobListing.js.map +1 -0
- package/package.json +6 -2
- package/src/browser/RuntimeConfig.ts +4 -0
- package/src/sites/upwork-com/upwork-com.actor.ts +78 -85
- package/src/sites/upwork-com/util/parseJobDetails.ts +573 -0
- package/src/sites/upwork-com/util/scrapeJobListing.ts +300 -0
|
@@ -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":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epsilon-asi/actors",
|
|
3
|
-
"version": "0.0.
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
63
|
+
await context.nav.goto(path, {
|
|
66
64
|
|
|
67
|
-
|
|
65
|
+
});
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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`
|