s3db.js 13.6.0 → 14.0.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/README.md +139 -43
- package/dist/s3db.cjs +72425 -38970
- package/dist/s3db.cjs.map +1 -1
- package/dist/s3db.es.js +72177 -38764
- package/dist/s3db.es.js.map +1 -1
- package/mcp/lib/base-handler.js +157 -0
- package/mcp/lib/handlers/connection-handler.js +280 -0
- package/mcp/lib/handlers/query-handler.js +533 -0
- package/mcp/lib/handlers/resource-handler.js +428 -0
- package/mcp/lib/tool-registry.js +336 -0
- package/mcp/lib/tools/connection-tools.js +161 -0
- package/mcp/lib/tools/query-tools.js +267 -0
- package/mcp/lib/tools/resource-tools.js +404 -0
- package/package.json +94 -49
- package/src/clients/memory-client.class.js +346 -191
- package/src/clients/memory-storage.class.js +300 -84
- package/src/clients/s3-client.class.js +7 -6
- package/src/concerns/geo-encoding.js +19 -2
- package/src/concerns/ip.js +59 -9
- package/src/concerns/money.js +8 -1
- package/src/concerns/password-hashing.js +49 -8
- package/src/concerns/plugin-storage.js +186 -18
- package/src/concerns/storage-drivers/filesystem-driver.js +284 -0
- package/src/database.class.js +139 -29
- package/src/errors.js +332 -42
- package/src/plugins/api/auth/oidc-auth.js +66 -17
- package/src/plugins/api/auth/strategies/base-strategy.class.js +74 -0
- package/src/plugins/api/auth/strategies/factory.class.js +63 -0
- package/src/plugins/api/auth/strategies/global-strategy.class.js +44 -0
- package/src/plugins/api/auth/strategies/path-based-strategy.class.js +83 -0
- package/src/plugins/api/auth/strategies/path-rules-strategy.class.js +118 -0
- package/src/plugins/api/concerns/failban-manager.js +106 -57
- package/src/plugins/api/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/route-context.js +601 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +180 -41
- package/src/plugins/api/routes/auth-routes.js +198 -30
- package/src/plugins/api/routes/resource-routes.js +19 -4
- package/src/plugins/api/server/health-manager.class.js +163 -0
- package/src/plugins/api/server/middleware-chain.class.js +310 -0
- package/src/plugins/api/server/router.class.js +472 -0
- package/src/plugins/api/server.js +280 -1303
- package/src/plugins/api/utils/custom-routes.js +17 -5
- package/src/plugins/api/utils/guards.js +76 -17
- package/src/plugins/api/utils/openapi-generator-cached.class.js +133 -0
- package/src/plugins/api/utils/openapi-generator.js +7 -6
- package/src/plugins/api/utils/template-engine.js +77 -3
- package/src/plugins/audit.plugin.js +30 -8
- package/src/plugins/backup.plugin.js +110 -14
- package/src/plugins/cache/cache.class.js +22 -5
- package/src/plugins/cache/filesystem-cache.class.js +116 -19
- package/src/plugins/cache/memory-cache.class.js +211 -57
- package/src/plugins/cache/multi-tier-cache.class.js +371 -0
- package/src/plugins/cache/partition-aware-filesystem-cache.class.js +168 -47
- package/src/plugins/cache/redis-cache.class.js +552 -0
- package/src/plugins/cache/s3-cache.class.js +17 -8
- package/src/plugins/cache.plugin.js +176 -61
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +60 -29
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/base-driver.js +16 -2
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +8 -1
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +8 -1
- package/src/plugins/cloud-inventory/index.js +29 -8
- package/src/plugins/cloud-inventory/registry.js +64 -42
- package/src/plugins/cloud-inventory.plugin.js +240 -138
- package/src/plugins/concerns/plugin-dependencies.js +54 -0
- package/src/plugins/concerns/resource-names.js +100 -0
- package/src/plugins/consumers/index.js +10 -2
- package/src/plugins/consumers/sqs-consumer.js +12 -2
- package/src/plugins/cookie-farm-suite.plugin.js +278 -0
- package/src/plugins/cookie-farm.errors.js +73 -0
- package/src/plugins/cookie-farm.plugin.js +869 -0
- package/src/plugins/costs.plugin.js +7 -1
- package/src/plugins/eventual-consistency/analytics.js +94 -19
- package/src/plugins/eventual-consistency/config.js +15 -7
- package/src/plugins/eventual-consistency/consolidation.js +29 -11
- package/src/plugins/eventual-consistency/garbage-collection.js +3 -1
- package/src/plugins/eventual-consistency/helpers.js +39 -14
- package/src/plugins/eventual-consistency/install.js +21 -2
- package/src/plugins/eventual-consistency/utils.js +32 -10
- package/src/plugins/fulltext.plugin.js +38 -11
- package/src/plugins/geo.plugin.js +61 -9
- package/src/plugins/identity/concerns/config.js +61 -0
- package/src/plugins/identity/concerns/mfa-manager.js +15 -2
- package/src/plugins/identity/concerns/rate-limit.js +124 -0
- package/src/plugins/identity/concerns/resource-schemas.js +9 -1
- package/src/plugins/identity/concerns/token-generator.js +29 -4
- package/src/plugins/identity/drivers/auth-driver.interface.js +76 -0
- package/src/plugins/identity/drivers/client-credentials-driver.js +127 -0
- package/src/plugins/identity/drivers/index.js +18 -0
- package/src/plugins/identity/drivers/password-driver.js +122 -0
- package/src/plugins/identity/email-service.js +17 -2
- package/src/plugins/identity/index.js +413 -69
- package/src/plugins/identity/oauth2-server.js +413 -30
- package/src/plugins/identity/oidc-discovery.js +16 -8
- package/src/plugins/identity/rsa-keys.js +115 -35
- package/src/plugins/identity/server.js +166 -45
- package/src/plugins/identity/session-manager.js +53 -7
- package/src/plugins/identity/ui/pages/mfa-verification.js +17 -15
- package/src/plugins/identity/ui/routes.js +363 -255
- package/src/plugins/importer/index.js +153 -20
- package/src/plugins/index.js +9 -2
- package/src/plugins/kubernetes-inventory/index.js +6 -0
- package/src/plugins/kubernetes-inventory/k8s-driver.js +867 -0
- package/src/plugins/kubernetes-inventory/resource-types.js +274 -0
- package/src/plugins/kubernetes-inventory.plugin.js +980 -0
- package/src/plugins/metrics.plugin.js +64 -16
- package/src/plugins/ml/base-model.class.js +25 -15
- package/src/plugins/ml/regression-model.class.js +1 -1
- package/src/plugins/ml.errors.js +57 -25
- package/src/plugins/ml.plugin.js +28 -4
- package/src/plugins/namespace.js +210 -0
- package/src/plugins/plugin.class.js +180 -8
- package/src/plugins/puppeteer/console-monitor.js +729 -0
- package/src/plugins/puppeteer/cookie-manager.js +492 -0
- package/src/plugins/puppeteer/network-monitor.js +816 -0
- package/src/plugins/puppeteer/performance-manager.js +746 -0
- package/src/plugins/puppeteer/proxy-manager.js +478 -0
- package/src/plugins/puppeteer/stealth-manager.js +556 -0
- package/src/plugins/puppeteer.errors.js +81 -0
- package/src/plugins/puppeteer.plugin.js +1327 -0
- package/src/plugins/queue-consumer.plugin.js +69 -14
- package/src/plugins/recon/behaviors/uptime-behavior.js +691 -0
- package/src/plugins/recon/concerns/command-runner.js +148 -0
- package/src/plugins/recon/concerns/diff-detector.js +372 -0
- package/src/plugins/recon/concerns/fingerprint-builder.js +307 -0
- package/src/plugins/recon/concerns/process-manager.js +338 -0
- package/src/plugins/recon/concerns/report-generator.js +478 -0
- package/src/plugins/recon/concerns/security-analyzer.js +571 -0
- package/src/plugins/recon/concerns/target-normalizer.js +68 -0
- package/src/plugins/recon/config/defaults.js +321 -0
- package/src/plugins/recon/config/resources.js +370 -0
- package/src/plugins/recon/index.js +778 -0
- package/src/plugins/recon/managers/dependency-manager.js +174 -0
- package/src/plugins/recon/managers/scheduler-manager.js +179 -0
- package/src/plugins/recon/managers/storage-manager.js +745 -0
- package/src/plugins/recon/managers/target-manager.js +274 -0
- package/src/plugins/recon/stages/asn-stage.js +314 -0
- package/src/plugins/recon/stages/certificate-stage.js +84 -0
- package/src/plugins/recon/stages/dns-stage.js +107 -0
- package/src/plugins/recon/stages/dnsdumpster-stage.js +362 -0
- package/src/plugins/recon/stages/fingerprint-stage.js +71 -0
- package/src/plugins/recon/stages/google-dorks-stage.js +440 -0
- package/src/plugins/recon/stages/http-stage.js +89 -0
- package/src/plugins/recon/stages/latency-stage.js +148 -0
- package/src/plugins/recon/stages/massdns-stage.js +302 -0
- package/src/plugins/recon/stages/osint-stage.js +1373 -0
- package/src/plugins/recon/stages/ports-stage.js +169 -0
- package/src/plugins/recon/stages/screenshot-stage.js +94 -0
- package/src/plugins/recon/stages/secrets-stage.js +514 -0
- package/src/plugins/recon/stages/subdomains-stage.js +295 -0
- package/src/plugins/recon/stages/tls-audit-stage.js +78 -0
- package/src/plugins/recon/stages/vulnerability-stage.js +78 -0
- package/src/plugins/recon/stages/web-discovery-stage.js +113 -0
- package/src/plugins/recon/stages/whois-stage.js +349 -0
- package/src/plugins/recon.plugin.js +75 -0
- package/src/plugins/recon.plugin.js.backup +2635 -0
- package/src/plugins/relation.errors.js +87 -14
- package/src/plugins/replicator.plugin.js +514 -137
- package/src/plugins/replicators/base-replicator.class.js +89 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +66 -22
- package/src/plugins/replicators/dynamodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mongodb-replicator.class.js +22 -15
- package/src/plugins/replicators/mysql-replicator.class.js +52 -17
- package/src/plugins/replicators/planetscale-replicator.class.js +30 -4
- package/src/plugins/replicators/postgres-replicator.class.js +62 -27
- package/src/plugins/replicators/s3db-replicator.class.js +25 -18
- package/src/plugins/replicators/schema-sync.helper.js +3 -3
- package/src/plugins/replicators/sqs-replicator.class.js +8 -2
- package/src/plugins/replicators/turso-replicator.class.js +23 -3
- package/src/plugins/replicators/webhook-replicator.class.js +42 -4
- package/src/plugins/s3-queue.plugin.js +464 -65
- package/src/plugins/scheduler.plugin.js +20 -6
- package/src/plugins/state-machine.plugin.js +40 -9
- package/src/plugins/tfstate/README.md +126 -126
- package/src/plugins/tfstate/base-driver.js +28 -4
- package/src/plugins/tfstate/errors.js +65 -10
- package/src/plugins/tfstate/filesystem-driver.js +52 -8
- package/src/plugins/tfstate/index.js +163 -90
- package/src/plugins/tfstate/s3-driver.js +64 -6
- package/src/plugins/ttl.plugin.js +72 -17
- package/src/plugins/vector/distances.js +18 -12
- package/src/plugins/vector/kmeans.js +26 -4
- package/src/resource.class.js +115 -19
- package/src/testing/factory.class.js +20 -3
- package/src/testing/seeder.class.js +7 -1
- package/src/clients/memory-client.md +0 -917
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +0 -449
|
@@ -0,0 +1,869 @@
|
|
|
1
|
+
import { Plugin } from './plugin.class.js';
|
|
2
|
+
import { PuppeteerPlugin } from './puppeteer.plugin.js';
|
|
3
|
+
import { requirePluginDependency } from './concerns/plugin-dependencies.js';
|
|
4
|
+
import tryFn from '../concerns/try-fn.js';
|
|
5
|
+
import { resolveResourceName } from './concerns/resource-names.js';
|
|
6
|
+
import { CookieFarmError, PersonaNotFoundError } from './cookie-farm.errors.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* CookieFarmPlugin - Persona Factory for Professional Web Scraping
|
|
10
|
+
*
|
|
11
|
+
* Creates and manages "personas" - complete browser identities with:
|
|
12
|
+
* - Cookies (farmed and aged)
|
|
13
|
+
* - Proxy binding (immutable)
|
|
14
|
+
* - User agent
|
|
15
|
+
* - Viewport configuration
|
|
16
|
+
* - Reputation score
|
|
17
|
+
* - Quality rating
|
|
18
|
+
*
|
|
19
|
+
* Use Cases:
|
|
20
|
+
* - Generate hundreds of trusted personas
|
|
21
|
+
* - Warmup sessions automatically
|
|
22
|
+
* - Rotate based on quality/age
|
|
23
|
+
* - Export/import for scaling
|
|
24
|
+
* - Integration with CrawlerPlugin
|
|
25
|
+
*
|
|
26
|
+
* @extends Plugin
|
|
27
|
+
*/
|
|
28
|
+
export class CookieFarmPlugin extends Plugin {
|
|
29
|
+
constructor(options = {}) {
|
|
30
|
+
super(options);
|
|
31
|
+
|
|
32
|
+
const resourceNamesOption = options.resourceNames || {};
|
|
33
|
+
|
|
34
|
+
// Default configuration
|
|
35
|
+
this.config = {
|
|
36
|
+
// Persona generation
|
|
37
|
+
generation: {
|
|
38
|
+
count: 10, // Initial personas to generate
|
|
39
|
+
proxies: [], // List of proxies to use
|
|
40
|
+
userAgentStrategy: 'random', // 'random' | 'desktop-only' | 'mobile-only'
|
|
41
|
+
viewportStrategy: 'varied', // 'varied' | 'fixed' | 'desktop-only'
|
|
42
|
+
...options.generation
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Warmup process
|
|
46
|
+
warmup: {
|
|
47
|
+
enabled: true,
|
|
48
|
+
sites: [
|
|
49
|
+
'https://www.google.com',
|
|
50
|
+
'https://www.youtube.com',
|
|
51
|
+
'https://www.wikipedia.org',
|
|
52
|
+
'https://www.reddit.com',
|
|
53
|
+
'https://www.amazon.com'
|
|
54
|
+
],
|
|
55
|
+
sitesPerPersona: 5,
|
|
56
|
+
randomOrder: true,
|
|
57
|
+
timePerSite: { min: 10000, max: 20000 },
|
|
58
|
+
interactions: {
|
|
59
|
+
scroll: true,
|
|
60
|
+
hover: true,
|
|
61
|
+
click: false // Safer - avoid accidental navigation
|
|
62
|
+
},
|
|
63
|
+
...options.warmup
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
// Quality scoring
|
|
67
|
+
quality: {
|
|
68
|
+
enabled: true,
|
|
69
|
+
factors: {
|
|
70
|
+
age: 0.3, // 30% weight
|
|
71
|
+
successRate: 0.4, // 40% weight
|
|
72
|
+
requestCount: 0.2, // 20% weight
|
|
73
|
+
warmupCompleted: 0.1 // 10% weight
|
|
74
|
+
},
|
|
75
|
+
thresholds: {
|
|
76
|
+
high: 0.8, // >= 80% = high quality
|
|
77
|
+
medium: 0.5, // >= 50% = medium quality
|
|
78
|
+
low: 0 // < 50% = low quality
|
|
79
|
+
},
|
|
80
|
+
...options.quality
|
|
81
|
+
},
|
|
82
|
+
|
|
83
|
+
// Rotation strategy
|
|
84
|
+
rotation: {
|
|
85
|
+
enabled: true,
|
|
86
|
+
maxAge: 86400000, // 24 hours
|
|
87
|
+
maxRequests: 200,
|
|
88
|
+
minQualityScore: 0.3,
|
|
89
|
+
retireOnFailureRate: 0.3, // Retire if success rate < 30%
|
|
90
|
+
...options.rotation
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// Storage
|
|
94
|
+
storage: {
|
|
95
|
+
resource: 'cookie_farm_personas',
|
|
96
|
+
encrypt: true,
|
|
97
|
+
...options.storage
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
// Export/Import
|
|
101
|
+
export: {
|
|
102
|
+
format: 'json', // 'json' | 'csv'
|
|
103
|
+
includeCredentials: false, // Mask proxy credentials
|
|
104
|
+
...options.export
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
// Stealth mode (anti-detection)
|
|
108
|
+
stealth: {
|
|
109
|
+
enabled: true,
|
|
110
|
+
timingProfile: 'normal', // 'very-slow' | 'slow' | 'normal' | 'fast'
|
|
111
|
+
consistentFingerprint: true, // Maintain consistent fingerprint per persona
|
|
112
|
+
executeJSChallenges: true, // Auto-solve JS challenges
|
|
113
|
+
humanBehavior: true, // Simulate mouse/scroll/typing
|
|
114
|
+
requestPacing: true, // Throttle requests to avoid rate limits
|
|
115
|
+
geoConsistency: true, // Match timezone/language to proxy geo
|
|
116
|
+
...options.stealth
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
this._storageResourceDescriptor = {
|
|
121
|
+
defaultName: 'plg_cookie_farm_personas',
|
|
122
|
+
override: resourceNamesOption.personas || options.storage?.resource
|
|
123
|
+
};
|
|
124
|
+
this.config.storage.resource = this._resolveStorageResourceName();
|
|
125
|
+
|
|
126
|
+
// Internal state
|
|
127
|
+
this.puppeteerPlugin = null;
|
|
128
|
+
this.stealthManager = null;
|
|
129
|
+
this.personaPool = new Map(); // personaId -> persona object
|
|
130
|
+
this.initialized = false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_resolveStorageResourceName() {
|
|
134
|
+
return resolveResourceName('cookiefarm', this._storageResourceDescriptor, {
|
|
135
|
+
namespace: this.namespace
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onNamespaceChanged() {
|
|
140
|
+
if (this.config?.storage) {
|
|
141
|
+
this.config.storage.resource = this._resolveStorageResourceName();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Install plugin and validate dependencies
|
|
147
|
+
*/
|
|
148
|
+
async onInstall() {
|
|
149
|
+
// Validate PuppeteerPlugin is installed (namespaced aware)
|
|
150
|
+
const puppeteerPlugin = this._findPuppeteerDependency();
|
|
151
|
+
|
|
152
|
+
if (!puppeteerPlugin) {
|
|
153
|
+
throw new CookieFarmError('PuppeteerPlugin is required before installing CookieFarmPlugin', {
|
|
154
|
+
pluginName: 'CookieFarmPlugin',
|
|
155
|
+
operation: 'onInstall',
|
|
156
|
+
statusCode: 400,
|
|
157
|
+
retriable: false,
|
|
158
|
+
suggestion: 'Install PuppeteerPlugin in db configuration before adding CookieFarmPlugin.'
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.puppeteerPlugin = puppeteerPlugin;
|
|
163
|
+
|
|
164
|
+
// Create personas storage resource
|
|
165
|
+
await this._setupPersonaStorage();
|
|
166
|
+
|
|
167
|
+
this.emit('cookieFarm.installed');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Locate PuppeteerPlugin dependency respecting namespaces
|
|
172
|
+
* @private
|
|
173
|
+
*/
|
|
174
|
+
_findPuppeteerDependency() {
|
|
175
|
+
const registries = [
|
|
176
|
+
this.database?.plugins,
|
|
177
|
+
this.database?.pluginRegistry,
|
|
178
|
+
Array.isArray(this.database?.pluginList) ? this.database.pluginList : null
|
|
179
|
+
].filter(Boolean);
|
|
180
|
+
|
|
181
|
+
const candidates = [];
|
|
182
|
+
for (const registry of registries) {
|
|
183
|
+
if (Array.isArray(registry)) {
|
|
184
|
+
candidates.push(...registry);
|
|
185
|
+
} else {
|
|
186
|
+
candidates.push(...Object.values(registry));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (candidates.length === 0) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const sameNamespace = candidates.find(
|
|
195
|
+
plugin => plugin instanceof PuppeteerPlugin &&
|
|
196
|
+
(plugin.namespace === this.namespace || !this.namespace)
|
|
197
|
+
);
|
|
198
|
+
if (sameNamespace) return sameNamespace;
|
|
199
|
+
|
|
200
|
+
return candidates.find(plugin => plugin instanceof PuppeteerPlugin) || null;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Start plugin
|
|
205
|
+
*/
|
|
206
|
+
async onStart() {
|
|
207
|
+
if (this.initialized) return;
|
|
208
|
+
|
|
209
|
+
// Initialize StealthManager if enabled
|
|
210
|
+
if (this.config.stealth.enabled) {
|
|
211
|
+
const { StealthManager } = await import('./puppeteer/stealth-manager.js');
|
|
212
|
+
this.stealthManager = new StealthManager(this);
|
|
213
|
+
this.emit('cookieFarm.stealthEnabled');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Load existing personas from storage
|
|
217
|
+
await this._loadPersonaPool();
|
|
218
|
+
|
|
219
|
+
// Generate initial personas if pool is empty
|
|
220
|
+
if (this.personaPool.size === 0 && this.config.generation.count > 0) {
|
|
221
|
+
this.emit('cookieFarm.generatingInitialPersonas', {
|
|
222
|
+
count: this.config.generation.count
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
await this.generatePersonas(this.config.generation.count);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
this.initialized = true;
|
|
229
|
+
this.emit('cookieFarm.started', {
|
|
230
|
+
personaCount: this.personaPool.size,
|
|
231
|
+
stealthEnabled: this.config.stealth.enabled
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Stop plugin
|
|
237
|
+
*/
|
|
238
|
+
async onStop() {
|
|
239
|
+
this.initialized = false;
|
|
240
|
+
this.emit('cookieFarm.stopped');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Uninstall plugin
|
|
245
|
+
*/
|
|
246
|
+
async onUninstall(options = {}) {
|
|
247
|
+
await this.onStop();
|
|
248
|
+
this.emit('cookieFarm.uninstalled');
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Setup persona storage resource
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
async _setupPersonaStorage() {
|
|
256
|
+
const resourceName = this.config.storage.resource;
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
await this.database.getResource(resourceName);
|
|
260
|
+
return;
|
|
261
|
+
} catch (err) {
|
|
262
|
+
if (!['ResourceNotFoundError', 'ResourceNotFound'].includes(err?.name)) {
|
|
263
|
+
throw err;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const [created, createErr] = await tryFn(() => this.database.createResource({
|
|
268
|
+
name: resourceName,
|
|
269
|
+
attributes: {
|
|
270
|
+
personaId: 'string|required',
|
|
271
|
+
sessionId: 'string|required',
|
|
272
|
+
proxyId: 'string|optional',
|
|
273
|
+
userAgent: 'string|required',
|
|
274
|
+
viewport: {
|
|
275
|
+
width: 'number|required',
|
|
276
|
+
height: 'number|required',
|
|
277
|
+
deviceScaleFactor: 'number'
|
|
278
|
+
},
|
|
279
|
+
cookies: 'array',
|
|
280
|
+
fingerprint: {
|
|
281
|
+
proxy: 'string',
|
|
282
|
+
userAgent: 'string',
|
|
283
|
+
viewport: 'string'
|
|
284
|
+
},
|
|
285
|
+
reputation: {
|
|
286
|
+
successCount: 'number',
|
|
287
|
+
failCount: 'number',
|
|
288
|
+
successRate: 'number',
|
|
289
|
+
totalRequests: 'number'
|
|
290
|
+
},
|
|
291
|
+
quality: {
|
|
292
|
+
score: 'number',
|
|
293
|
+
rating: 'string',
|
|
294
|
+
lastCalculated: 'number'
|
|
295
|
+
},
|
|
296
|
+
metadata: {
|
|
297
|
+
createdAt: 'number',
|
|
298
|
+
lastUsed: 'number',
|
|
299
|
+
expiresAt: 'number',
|
|
300
|
+
age: 'number',
|
|
301
|
+
warmupCompleted: 'boolean',
|
|
302
|
+
retired: 'boolean'
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
timestamps: true,
|
|
306
|
+
behavior: 'body-only',
|
|
307
|
+
partitions: {
|
|
308
|
+
byQuality: {
|
|
309
|
+
fields: { 'quality.rating': 'string' }
|
|
310
|
+
},
|
|
311
|
+
byProxy: {
|
|
312
|
+
fields: { proxyId: 'string' }
|
|
313
|
+
},
|
|
314
|
+
byRetirement: {
|
|
315
|
+
fields: { 'metadata.retired': 'boolean' }
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}));
|
|
319
|
+
|
|
320
|
+
if (!created) {
|
|
321
|
+
const existing = this.database.resources?.[resourceName];
|
|
322
|
+
if (!existing) {
|
|
323
|
+
throw createErr;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Load persona pool from storage
|
|
330
|
+
* @private
|
|
331
|
+
*/
|
|
332
|
+
async _loadPersonaPool() {
|
|
333
|
+
const storage = await this.database.getResource(this.config.storage.resource);
|
|
334
|
+
const personas = await storage.list({ limit: 1000 });
|
|
335
|
+
|
|
336
|
+
for (const persona of personas) {
|
|
337
|
+
this.personaPool.set(persona.personaId, persona);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.emit('cookieFarm.personasLoaded', {
|
|
341
|
+
count: this.personaPool.size
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Generate new personas
|
|
347
|
+
* @param {number} count - Number of personas to generate
|
|
348
|
+
* @param {Object} options - Generation options
|
|
349
|
+
* @returns {Promise<Array>}
|
|
350
|
+
*/
|
|
351
|
+
async generatePersonas(count = 1, options = {}) {
|
|
352
|
+
const {
|
|
353
|
+
proxies = this.config.generation.proxies,
|
|
354
|
+
warmup = this.config.warmup.enabled
|
|
355
|
+
} = options;
|
|
356
|
+
|
|
357
|
+
const generatedPersonas = [];
|
|
358
|
+
|
|
359
|
+
for (let i = 0; i < count; i++) {
|
|
360
|
+
const persona = await this._createPersona(proxies);
|
|
361
|
+
generatedPersonas.push(persona);
|
|
362
|
+
|
|
363
|
+
this.emit('cookieFarm.personaCreated', {
|
|
364
|
+
personaId: persona.personaId,
|
|
365
|
+
proxyId: persona.proxyId
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Warmup if enabled
|
|
369
|
+
if (warmup) {
|
|
370
|
+
await this.warmupPersona(persona.personaId);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
this.emit('cookieFarm.personasGenerated', {
|
|
375
|
+
count: generatedPersonas.length
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return generatedPersonas;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Create a single persona
|
|
383
|
+
* @private
|
|
384
|
+
* @param {Array} proxies - Available proxies
|
|
385
|
+
* @returns {Promise<Object>}
|
|
386
|
+
*/
|
|
387
|
+
async _createPersona(proxies = []) {
|
|
388
|
+
const personaId = `persona_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
389
|
+
const sessionId = `session_${personaId}`;
|
|
390
|
+
|
|
391
|
+
// Generate user agent
|
|
392
|
+
const userAgent = this._generateUserAgent();
|
|
393
|
+
|
|
394
|
+
// Generate viewport
|
|
395
|
+
const viewport = this._generateViewport();
|
|
396
|
+
|
|
397
|
+
// Create fingerprint
|
|
398
|
+
const fingerprint = {
|
|
399
|
+
userAgent: userAgent,
|
|
400
|
+
viewport: `${viewport.width}x${viewport.height}`,
|
|
401
|
+
proxy: null
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
// Assign proxy if available
|
|
405
|
+
let proxyId = null;
|
|
406
|
+
if (proxies.length > 0 && this.puppeteerPlugin.config.proxy.enabled) {
|
|
407
|
+
// This will create immutable binding
|
|
408
|
+
const proxy = this.puppeteerPlugin.proxyManager.getProxyForSession(sessionId, true);
|
|
409
|
+
proxyId = proxy?.id || null;
|
|
410
|
+
fingerprint.proxy = proxyId;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const persona = {
|
|
414
|
+
personaId,
|
|
415
|
+
sessionId,
|
|
416
|
+
proxyId,
|
|
417
|
+
userAgent,
|
|
418
|
+
viewport,
|
|
419
|
+
cookies: [],
|
|
420
|
+
fingerprint,
|
|
421
|
+
reputation: {
|
|
422
|
+
successCount: 0,
|
|
423
|
+
failCount: 0,
|
|
424
|
+
successRate: 1.0,
|
|
425
|
+
totalRequests: 0
|
|
426
|
+
},
|
|
427
|
+
quality: {
|
|
428
|
+
score: 0,
|
|
429
|
+
rating: 'low',
|
|
430
|
+
lastCalculated: Date.now()
|
|
431
|
+
},
|
|
432
|
+
metadata: {
|
|
433
|
+
createdAt: Date.now(),
|
|
434
|
+
lastUsed: null,
|
|
435
|
+
expiresAt: Date.now() + this.config.rotation.maxAge,
|
|
436
|
+
age: 0,
|
|
437
|
+
warmupCompleted: false,
|
|
438
|
+
retired: false
|
|
439
|
+
}
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
// Save to storage
|
|
443
|
+
const storage = this.database.getResource(this.config.storage.resource);
|
|
444
|
+
await storage.insert(persona);
|
|
445
|
+
|
|
446
|
+
// Add to pool
|
|
447
|
+
this.personaPool.set(personaId, persona);
|
|
448
|
+
|
|
449
|
+
return persona;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Generate user agent based on strategy
|
|
454
|
+
* @private
|
|
455
|
+
*/
|
|
456
|
+
_generateUserAgent() {
|
|
457
|
+
const strategy = this.config.generation.userAgentStrategy;
|
|
458
|
+
|
|
459
|
+
// Use PuppeteerPlugin's user agent generation
|
|
460
|
+
// For now, return a default - will enhance later
|
|
461
|
+
const desktopAgents = [
|
|
462
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
463
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
464
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
|
|
465
|
+
];
|
|
466
|
+
|
|
467
|
+
return desktopAgents[Math.floor(Math.random() * desktopAgents.length)];
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Generate viewport based on strategy
|
|
472
|
+
* @private
|
|
473
|
+
*/
|
|
474
|
+
_generateViewport() {
|
|
475
|
+
const strategy = this.config.generation.viewportStrategy;
|
|
476
|
+
|
|
477
|
+
const viewports = [
|
|
478
|
+
{ width: 1920, height: 1080, deviceScaleFactor: 1 },
|
|
479
|
+
{ width: 1680, height: 1050, deviceScaleFactor: 1 },
|
|
480
|
+
{ width: 1440, height: 900, deviceScaleFactor: 1 },
|
|
481
|
+
{ width: 1366, height: 768, deviceScaleFactor: 1 },
|
|
482
|
+
{ width: 1280, height: 800, deviceScaleFactor: 1 }
|
|
483
|
+
];
|
|
484
|
+
|
|
485
|
+
return viewports[Math.floor(Math.random() * viewports.length)];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Warmup a persona by visiting trusted sites
|
|
490
|
+
* @param {string} personaId - Persona identifier
|
|
491
|
+
* @returns {Promise<void>}
|
|
492
|
+
*/
|
|
493
|
+
async warmupPersona(personaId) {
|
|
494
|
+
const persona = this.personaPool.get(personaId);
|
|
495
|
+
if (!persona) {
|
|
496
|
+
throw new PersonaNotFoundError(personaId);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
if (persona.metadata.warmupCompleted) {
|
|
500
|
+
this.emit('cookieFarm.warmupSkipped', {
|
|
501
|
+
personaId,
|
|
502
|
+
reason: 'already completed'
|
|
503
|
+
});
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
this.emit('cookieFarm.warmupStarted', { personaId });
|
|
508
|
+
|
|
509
|
+
const sites = [...this.config.warmup.sites];
|
|
510
|
+
const sitesToVisit = sites.slice(0, this.config.warmup.sitesPerPersona);
|
|
511
|
+
|
|
512
|
+
if (this.config.warmup.randomOrder) {
|
|
513
|
+
sitesToVisit.sort(() => Math.random() - 0.5);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
for (const url of sitesToVisit) {
|
|
517
|
+
try {
|
|
518
|
+
await this._visitSite(persona, url);
|
|
519
|
+
|
|
520
|
+
this.emit('cookieFarm.warmupSiteCompleted', {
|
|
521
|
+
personaId,
|
|
522
|
+
url
|
|
523
|
+
});
|
|
524
|
+
} catch (err) {
|
|
525
|
+
this.emit('cookieFarm.warmupSiteFailed', {
|
|
526
|
+
personaId,
|
|
527
|
+
url,
|
|
528
|
+
error: err.message
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// Mark warmup as completed
|
|
534
|
+
persona.metadata.warmupCompleted = true;
|
|
535
|
+
|
|
536
|
+
// Recalculate quality
|
|
537
|
+
await this._calculateQuality(persona);
|
|
538
|
+
|
|
539
|
+
// Save to storage
|
|
540
|
+
await this._savePersona(persona);
|
|
541
|
+
|
|
542
|
+
this.emit('cookieFarm.warmupCompleted', { personaId });
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Visit a site with persona
|
|
547
|
+
* @private
|
|
548
|
+
*/
|
|
549
|
+
async _visitSite(persona, url) {
|
|
550
|
+
const page = await this.puppeteerPlugin.navigate(url, {
|
|
551
|
+
useSession: persona.sessionId
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
// Random time on site
|
|
555
|
+
const timeOnSite = this.config.warmup.timePerSite.min +
|
|
556
|
+
Math.random() * (this.config.warmup.timePerSite.max - this.config.warmup.timePerSite.min);
|
|
557
|
+
|
|
558
|
+
// Perform interactions
|
|
559
|
+
if (this.config.warmup.interactions.scroll) {
|
|
560
|
+
await page.humanScroll({ direction: 'down' });
|
|
561
|
+
await this._delay(1000);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (this.config.warmup.interactions.hover) {
|
|
565
|
+
try {
|
|
566
|
+
const elements = await page.$$('a, button');
|
|
567
|
+
if (elements.length > 0) {
|
|
568
|
+
const randomElement = elements[Math.floor(Math.random() * elements.length)];
|
|
569
|
+
await randomElement.hover();
|
|
570
|
+
}
|
|
571
|
+
} catch (err) {
|
|
572
|
+
// Ignore hover errors
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// Wait remaining time
|
|
577
|
+
await this._delay(timeOnSite);
|
|
578
|
+
|
|
579
|
+
// Update reputation
|
|
580
|
+
persona.reputation.successCount++;
|
|
581
|
+
persona.reputation.totalRequests++;
|
|
582
|
+
persona.reputation.successRate = persona.reputation.successCount / persona.reputation.totalRequests;
|
|
583
|
+
persona.metadata.lastUsed = Date.now();
|
|
584
|
+
|
|
585
|
+
await page.close();
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Calculate quality score for persona
|
|
590
|
+
* @private
|
|
591
|
+
*/
|
|
592
|
+
async _calculateQuality(persona) {
|
|
593
|
+
if (!this.config.quality.enabled) {
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const factors = this.config.quality.factors;
|
|
598
|
+
let score = 0;
|
|
599
|
+
|
|
600
|
+
// Age factor (older = better, up to 24h)
|
|
601
|
+
const ageInHours = persona.metadata.age / (1000 * 60 * 60);
|
|
602
|
+
const ageScore = Math.min(ageInHours / 24, 1);
|
|
603
|
+
score += ageScore * factors.age;
|
|
604
|
+
|
|
605
|
+
// Success rate factor
|
|
606
|
+
score += persona.reputation.successRate * factors.successRate;
|
|
607
|
+
|
|
608
|
+
// Request count factor (more requests = better, up to maxRequests)
|
|
609
|
+
const requestScore = Math.min(
|
|
610
|
+
persona.reputation.totalRequests / this.config.rotation.maxRequests,
|
|
611
|
+
1
|
|
612
|
+
);
|
|
613
|
+
score += requestScore * factors.requestCount;
|
|
614
|
+
|
|
615
|
+
// Warmup completed factor
|
|
616
|
+
const warmupScore = persona.metadata.warmupCompleted ? 1 : 0;
|
|
617
|
+
score += warmupScore * factors.warmupCompleted;
|
|
618
|
+
|
|
619
|
+
// Normalize to 0-1
|
|
620
|
+
persona.quality.score = score;
|
|
621
|
+
|
|
622
|
+
// Determine rating
|
|
623
|
+
const thresholds = this.config.quality.thresholds;
|
|
624
|
+
if (score >= thresholds.high) {
|
|
625
|
+
persona.quality.rating = 'high';
|
|
626
|
+
} else if (score >= thresholds.medium) {
|
|
627
|
+
persona.quality.rating = 'medium';
|
|
628
|
+
} else {
|
|
629
|
+
persona.quality.rating = 'low';
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
persona.quality.lastCalculated = Date.now();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Save persona to storage
|
|
637
|
+
* @private
|
|
638
|
+
*/
|
|
639
|
+
async _savePersona(persona) {
|
|
640
|
+
persona.metadata.age = Date.now() - persona.metadata.createdAt;
|
|
641
|
+
|
|
642
|
+
const storage = this.database.getResource(this.config.storage.resource);
|
|
643
|
+
await storage.update(persona.id, persona);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/**
|
|
647
|
+
* Get persona by criteria
|
|
648
|
+
* @param {Object} criteria - Selection criteria
|
|
649
|
+
* @returns {Promise<Object|null>}
|
|
650
|
+
*/
|
|
651
|
+
async getPersona(criteria = {}) {
|
|
652
|
+
const {
|
|
653
|
+
quality = null, // 'low' | 'medium' | 'high'
|
|
654
|
+
minQualityScore = 0,
|
|
655
|
+
proxyId = null,
|
|
656
|
+
excludeRetired = true
|
|
657
|
+
} = criteria;
|
|
658
|
+
|
|
659
|
+
const candidates = Array.from(this.personaPool.values()).filter(persona => {
|
|
660
|
+
// Exclude retired
|
|
661
|
+
if (excludeRetired && persona.metadata.retired) {
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Quality filter
|
|
666
|
+
if (quality && persona.quality.rating !== quality) {
|
|
667
|
+
return false;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Min quality score
|
|
671
|
+
if (persona.quality.score < minQualityScore) {
|
|
672
|
+
return false;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// Proxy filter
|
|
676
|
+
if (proxyId && persona.proxyId !== proxyId) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return true;
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
if (candidates.length === 0) {
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// Sort by quality score descending
|
|
688
|
+
candidates.sort((a, b) => b.quality.score - a.quality.score);
|
|
689
|
+
|
|
690
|
+
return candidates[0];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/**
|
|
694
|
+
* Record persona usage
|
|
695
|
+
* @param {string} personaId - Persona identifier
|
|
696
|
+
* @param {Object} result - Usage result
|
|
697
|
+
*/
|
|
698
|
+
async recordUsage(personaId, result = {}) {
|
|
699
|
+
const { success = true } = result;
|
|
700
|
+
|
|
701
|
+
const persona = this.personaPool.get(personaId);
|
|
702
|
+
if (!persona) {
|
|
703
|
+
throw new PersonaNotFoundError(personaId);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
persona.reputation.totalRequests++;
|
|
707
|
+
persona.metadata.lastUsed = Date.now();
|
|
708
|
+
|
|
709
|
+
if (success) {
|
|
710
|
+
persona.reputation.successCount++;
|
|
711
|
+
} else {
|
|
712
|
+
persona.reputation.failCount++;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
persona.reputation.successRate = persona.reputation.successCount / persona.reputation.totalRequests;
|
|
716
|
+
|
|
717
|
+
// Recalculate quality
|
|
718
|
+
await this._calculateQuality(persona);
|
|
719
|
+
|
|
720
|
+
// Check if should retire
|
|
721
|
+
if (this._shouldRetire(persona)) {
|
|
722
|
+
await this.retirePersona(personaId);
|
|
723
|
+
} else {
|
|
724
|
+
await this._savePersona(persona);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
this.emit('cookieFarm.usageRecorded', {
|
|
728
|
+
personaId,
|
|
729
|
+
success,
|
|
730
|
+
quality: persona.quality
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* Check if persona should be retired
|
|
736
|
+
* @private
|
|
737
|
+
*/
|
|
738
|
+
_shouldRetire(persona) {
|
|
739
|
+
if (!this.config.rotation.enabled) {
|
|
740
|
+
return false;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Check age
|
|
744
|
+
if (Date.now() > persona.metadata.expiresAt) {
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
// Check request count
|
|
749
|
+
if (persona.reputation.totalRequests >= this.config.rotation.maxRequests) {
|
|
750
|
+
return true;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Check success rate
|
|
754
|
+
if (persona.reputation.successRate < this.config.rotation.retireOnFailureRate) {
|
|
755
|
+
return true;
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// Check quality score
|
|
759
|
+
if (persona.quality.score < this.config.rotation.minQualityScore) {
|
|
760
|
+
return true;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
return false;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Retire a persona
|
|
768
|
+
* @param {string} personaId - Persona identifier
|
|
769
|
+
*/
|
|
770
|
+
async retirePersona(personaId) {
|
|
771
|
+
const persona = this.personaPool.get(personaId);
|
|
772
|
+
if (!persona) {
|
|
773
|
+
throw new PersonaNotFoundError(personaId);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
persona.metadata.retired = true;
|
|
777
|
+
await this._savePersona(persona);
|
|
778
|
+
|
|
779
|
+
this.emit('cookieFarm.personaRetired', {
|
|
780
|
+
personaId,
|
|
781
|
+
reason: 'rotation policy'
|
|
782
|
+
});
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
/**
|
|
786
|
+
* Get statistics
|
|
787
|
+
* @returns {Promise<Object>}
|
|
788
|
+
*/
|
|
789
|
+
async getStats() {
|
|
790
|
+
const personas = Array.from(this.personaPool.values());
|
|
791
|
+
|
|
792
|
+
const stats = {
|
|
793
|
+
total: personas.length,
|
|
794
|
+
active: 0,
|
|
795
|
+
retired: 0,
|
|
796
|
+
byQuality: { high: 0, medium: 0, low: 0 },
|
|
797
|
+
byProxy: {},
|
|
798
|
+
warmupCompleted: 0,
|
|
799
|
+
averageQualityScore: 0,
|
|
800
|
+
averageSuccessRate: 0,
|
|
801
|
+
totalRequests: 0
|
|
802
|
+
};
|
|
803
|
+
|
|
804
|
+
let qualitySum = 0;
|
|
805
|
+
let successRateSum = 0;
|
|
806
|
+
|
|
807
|
+
for (const persona of personas) {
|
|
808
|
+
if (persona.metadata.retired) {
|
|
809
|
+
stats.retired++;
|
|
810
|
+
} else {
|
|
811
|
+
stats.active++;
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
stats.byQuality[persona.quality.rating]++;
|
|
815
|
+
|
|
816
|
+
if (persona.proxyId) {
|
|
817
|
+
stats.byProxy[persona.proxyId] = (stats.byProxy[persona.proxyId] || 0) + 1;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
if (persona.metadata.warmupCompleted) {
|
|
821
|
+
stats.warmupCompleted++;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
qualitySum += persona.quality.score;
|
|
825
|
+
successRateSum += persona.reputation.successRate;
|
|
826
|
+
stats.totalRequests += persona.reputation.totalRequests;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (personas.length > 0) {
|
|
830
|
+
stats.averageQualityScore = qualitySum / personas.length;
|
|
831
|
+
stats.averageSuccessRate = successRateSum / personas.length;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return stats;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
/**
|
|
838
|
+
* Export personas
|
|
839
|
+
* @param {Object} options - Export options
|
|
840
|
+
* @returns {Promise<Array>}
|
|
841
|
+
*/
|
|
842
|
+
async exportPersonas(options = {}) {
|
|
843
|
+
const { includeRetired = false, format = this.config.export.format } = options;
|
|
844
|
+
|
|
845
|
+
const personas = Array.from(this.personaPool.values())
|
|
846
|
+
.filter(persona => includeRetired || !persona.metadata.retired);
|
|
847
|
+
|
|
848
|
+
// Mask credentials if needed
|
|
849
|
+
if (!this.config.export.includeCredentials) {
|
|
850
|
+
personas.forEach(persona => {
|
|
851
|
+
if (persona.fingerprint.proxy) {
|
|
852
|
+
persona.fingerprint.proxy = persona.proxyId; // Just ID, not full URL
|
|
853
|
+
}
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
return personas;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Delay helper
|
|
862
|
+
* @private
|
|
863
|
+
*/
|
|
864
|
+
async _delay(ms) {
|
|
865
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
export default CookieFarmPlugin;
|