52 Commits

Author SHA1 Message Date
DebaucheryLibrarian
a3a2de2e04 1.254.15 2026-07-09 00:34:18 +02:00
DebaucheryLibrarian
56b18618be Showing number of newly added creators in ManyVids seed. 2026-07-09 00:34:16 +02:00
DebaucheryLibrarian
a68c8539fd 1.254.14 2026-07-09 00:16:09 +02:00
DebaucheryLibrarian
a3ae82dcd3 Added sorting env to ManyVids seed, changed default to followers instead of top. 2026-07-09 00:16:07 +02:00
DebaucheryLibrarian
c3dad0b523 Moved ManyVids exclude config to networks. 2026-07-08 23:57:25 +02:00
DebaucheryLibrarian
25af9152d7 1.254.13 2026-07-08 23:54:18 +02:00
DebaucheryLibrarian
8060be166b Added ManyVids API seed. 2026-07-08 23:54:16 +02:00
DebaucheryLibrarian
46fcd1465a Excluding ManyVids while still working on channel aggregation. 2026-07-08 21:45:45 +02:00
DebaucheryLibrarian
42027d0856 1.254.12 2026-07-08 21:44:43 +02:00
DebaucheryLibrarian
70d4a05aeb Changed Jax Slayher entry ID from slug to data ID due URL conflicts. 2026-07-08 21:44:41 +02:00
DebaucheryLibrarian
b2941940e3 Added ManyVids with fixed channels, WIP. 2026-07-08 21:37:58 +02:00
DebaucheryLibrarian
270cc36e65 1.254.11 2026-07-08 05:16:51 +02:00
DebaucheryLibrarian
6fa03081ef Fixed Whale Member scraper for Nuxt URLs, added P2P Marketplace. 2026-07-08 05:16:48 +02:00
DebaucheryLibrarian
4a60cadb4b 1.254.10 2026-07-08 04:38:05 +02:00
DebaucheryLibrarian
d4db2ee1e4 Added Glowing Desire, adapted Nubiles scraper. 2026-07-08 04:38:02 +02:00
DebaucheryLibrarian
76e6e60d69 1.254.9 2026-07-08 04:04:11 +02:00
DebaucheryLibrarian
7aa7b9bef1 Fixed 8KMembers / Fidelity API response bug. 2026-07-08 04:04:09 +02:00
DebaucheryLibrarian
04d1e340f5 1.254.8 2026-07-08 03:51:30 +02:00
DebaucheryLibrarian
a5c91ad816 Added finger DP tag. 2026-07-08 03:51:26 +02:00
DebaucheryLibrarian
35c17d32bb 1.254.7 2026-07-08 02:33:57 +02:00
DebaucheryLibrarian
bfb7c8b080 Added Jax Slayher's site. 2026-07-08 02:33:54 +02:00
DebaucheryLibrarian
be27b325ca Added anal spitroast tag priority. 2026-07-07 04:28:24 +02:00
DebaucheryLibrarian
969c576eb8 1.254.6 2026-07-07 03:40:21 +02:00
DebaucheryLibrarian
66e0076083 Resolving aliased tag media. 2026-07-07 03:40:19 +02:00
DebaucheryLibrarian
1143f0865d Added optional tag alias slug for redirect purposes. 2026-07-07 00:49:21 +02:00
DebaucheryLibrarian
da7e30ff59 1.254.5 2026-07-07 00:45:25 +02:00
DebaucheryLibrarian
242b7d9271 Added rename to tag seed. 2026-07-07 00:45:20 +02:00
DebaucheryLibrarian
b5b87ff82e 1.254.4 2026-07-06 22:29:37 +02:00
DebaucheryLibrarian
99e1efe5c5 Added enhanced and natural boobs tag priorities 2026-07-06 22:29:35 +02:00
DebaucheryLibrarian
6d1f5a9130 Added ass to pussy tags. 2026-07-06 17:20:52 +02:00
DebaucheryLibrarian
e9500f6d0e Removed superfluous media join in biometrics. 2026-07-06 00:26:03 +02:00
DebaucheryLibrarian
db8edf1010 Fixed avatar source in biometrics query. 2026-07-06 00:24:05 +02:00
DebaucheryLibrarian
f538d8b62a Removed superfluous actor_id from biometrics query, should prevent dupes being processed unnecessarily. 2026-07-06 00:20:32 +02:00
DebaucheryLibrarian
e0a89f0b15 Mapped biometric decode error to media ID. 2026-07-06 00:12:27 +02:00
DebaucheryLibrarian
b895ce5a49 1.254.3 2026-07-06 00:09:41 +02:00
DebaucheryLibrarian
b9a8d49063 Biometric time remaining accounting for skipped files. 2026-07-06 00:09:39 +02:00
DebaucheryLibrarian
708c02b410 Tensor dispose optionally chained in case it wasn't set. 2026-07-05 20:57:40 +02:00
DebaucheryLibrarian
c6a3c3aba3 1.254.2 2026-07-05 20:57:03 +02:00
DebaucheryLibrarian
fe2b63878c Catching biometric tensor decode errors. 2026-07-05 20:57:00 +02:00
DebaucheryLibrarian
dd48ef9194 1.254.1 2026-07-05 04:41:38 +02:00
DebaucheryLibrarian
a888acf0b7 Fixed missing slash in biometrics bucket path. 2026-07-05 04:41:36 +02:00
DebaucheryLibrarian
b66ee3095d 1.254.0 2026-07-05 04:02:10 +02:00
DebaucheryLibrarian
525bca255b Added biometrics for avatars. 2026-07-05 04:02:00 +02:00
DebaucheryLibrarian
32a3f876e3 Added abilities seed file to repo. 2026-06-17 00:58:21 +02:00
DebaucheryLibrarian
ccf815b71f 1.253.6 2026-06-17 00:57:48 +02:00
DebaucheryLibrarian
da6b54079f Hashing media in transform in an attempt to improve reliability. Added seed file for user abilities. 2026-06-17 00:57:45 +02:00
DebaucheryLibrarian
43d58ff093 1.253.5 2026-06-09 05:54:11 +02:00
DebaucheryLibrarian
677f72df33 Utilizing allow global match into scene actor association. 2026-06-09 05:54:09 +02:00
DebaucheryLibrarian
18d6832f95 1.253.4 2026-06-09 04:23:16 +02:00
DebaucheryLibrarian
170c42c282 Updated common. 2026-06-09 04:23:11 +02:00
DebaucheryLibrarian
51eafc9a07 1.253.3 2026-06-09 03:19:10 +02:00
DebaucheryLibrarian
01213afd8b Updated common. 2026-06-09 03:19:08 +02:00
24 changed files with 1705 additions and 111 deletions

2
common

Submodule common updated: 1374f90397...ec0812ad9d

View File

@@ -181,6 +181,8 @@ module.exports = {
'traxxx', 'traxxx',
// porn doe // porn doe
'forbondage', 'forbondage',
// prevent huge influx
'manyvids',
], ],
}, },
profiles: null, profiles: null,
@@ -286,6 +288,9 @@ module.exports = {
excludeHostnames: [], excludeHostnames: [],
selectIndex: {}, selectIndex: {},
}, },
biometrics: {
enabled: true,
},
}, },
titleSlugLength: 50, titleSlugLength: 50,
}; };

View File

@@ -76,7 +76,7 @@ exports.up = async (knex) => {
}); });
await knex.schema.createTable('media', (table) => { await knex.schema.createTable('media', (table) => {
table.text('id', 21) table.string('id', 21)
.primary(); .primary();
table.text('path'); table.text('path');

View File

@@ -0,0 +1,35 @@
exports.up = async function(knex) {
await knex.schema.createTable('media_biometrics', (table) => {
table.increments('id');
table.string('media_id', 21)
.references('id')
.inTable('media')
.notNullable()
.onDelete('cascade');
table.integer('width');
table.integer('height');
table.integer('face_index')
.notNullable()
.defaultTo(0);
table.json('biometrics');
table.specificType('embedding', 'vector(1024)');
table.datetime('updated_at')
.notNullable()
.defaultTo(knex.fn.now());
table.datetime('created_at')
.notNullable()
.defaultTo(knex.fn.now());
table.unique(['media_id', 'face_index']);
});
};
exports.down = async function(knex) {
await knex.schema.dropTable('media_biometrics');
};

665
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.253.2", "version": "1.254.15",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "traxxx", "name": "traxxx",
"version": "1.253.2", "version": "1.254.15",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.458.0", "@aws-sdk/client-s3": "^3.458.0",
@@ -17,6 +17,9 @@
"@graphile-contrib/pg-order-by-related": "^1.0.0", "@graphile-contrib/pg-order-by-related": "^1.0.0",
"@graphile-contrib/pg-simplify-inflector": "^6.1.0", "@graphile-contrib/pg-simplify-inflector": "^6.1.0",
"@graphile/pg-aggregates": "^0.1.1", "@graphile/pg-aggregates": "^0.1.1",
"@rsc-parser/react-client": "^1.1.2",
"@tensorflow/tfjs-node": "^4.22.0",
"@vladmandic/human": "^3.3.6",
"acorn": "^8.11.2", "acorn": "^8.11.2",
"array-equal": "^1.0.2", "array-equal": "^1.0.2",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
@@ -25,7 +28,6 @@
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
"canvas": "^2.11.2",
"casual": "^1.6.2", "casual": "^1.6.2",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cli-confirm": "^1.0.1", "cli-confirm": "^1.0.1",
@@ -3811,6 +3813,8 @@
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"detect-libc": "^2.0.0", "detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0",
@@ -3830,6 +3834,8 @@
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"agent-base": "6", "agent-base": "6",
"debug": "4" "debug": "4"
@@ -3842,6 +3848,8 @@
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"yallist": "^4.0.0" "yallist": "^4.0.0"
}, },
@@ -3853,6 +3861,8 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"semver": "^6.0.0" "semver": "^6.0.0"
}, },
@@ -3867,6 +3877,8 @@
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"optional": true,
"peer": true,
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -3875,6 +3887,8 @@
"version": "2.7.0", "version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
}, },
@@ -3894,6 +3908,8 @@
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"lru-cache": "^6.0.0" "lru-cache": "^6.0.0"
}, },
@@ -3907,17 +3923,23 @@
"node_modules/@mapbox/node-pre-gyp/node_modules/tr46": { "node_modules/@mapbox/node-pre-gyp/node_modules/tr46": {
"version": "0.0.3", "version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"optional": true,
"peer": true
}, },
"node_modules/@mapbox/node-pre-gyp/node_modules/webidl-conversions": { "node_modules/@mapbox/node-pre-gyp/node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"optional": true,
"peer": true
}, },
"node_modules/@mapbox/node-pre-gyp/node_modules/whatwg-url": { "node_modules/@mapbox/node-pre-gyp/node_modules/whatwg-url": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"tr46": "~0.0.3", "tr46": "~0.0.3",
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
@@ -3926,7 +3948,9 @@
"node_modules/@mapbox/node-pre-gyp/node_modules/yallist": { "node_modules/@mapbox/node-pre-gyp/node_modules/yallist": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"optional": true,
"peer": true
}, },
"node_modules/@nicolo-ribaudo/chokidar-2": { "node_modules/@nicolo-ribaudo/chokidar-2": {
"version": "2.1.8-no-fsevents.3", "version": "2.1.8-no-fsevents.3",
@@ -4181,6 +4205,11 @@
"@redis/client": "^1.0.0" "@redis/client": "^1.0.0"
} }
}, },
"node_modules/@rsc-parser/react-client": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@rsc-parser/react-client/-/react-client-1.1.2.tgz",
"integrity": "sha512-IXW7ViicNACwI1vxAQgndtRKZlM9HgGpxSZdqnXn8fCY2VvUkqas03WuHv4LnVlCdC3HGwhbh3r3TBLxRrYSgw=="
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -5051,6 +5080,27 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
}, },
"node_modules/@tensorflow/tfjs": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs/-/tfjs-4.22.0.tgz",
"integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@tensorflow/tfjs-backend-webgl": "4.22.0",
"@tensorflow/tfjs-converter": "4.22.0",
"@tensorflow/tfjs-core": "4.22.0",
"@tensorflow/tfjs-data": "4.22.0",
"@tensorflow/tfjs-layers": "4.22.0",
"argparse": "^1.0.10",
"chalk": "^4.1.0",
"core-js": "3.29.1",
"regenerator-runtime": "^0.13.5",
"yargs": "^16.0.3"
},
"bin": {
"tfjs-custom-module": "dist/tools/custom_module/cli.js"
}
},
"node_modules/@tensorflow/tfjs-core": { "node_modules/@tensorflow/tfjs-core": {
"version": "1.7.0", "version": "1.7.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz", "resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-1.7.0.tgz",
@@ -5075,6 +5125,493 @@
"node": "4.x || >=6.0.0" "node": "4.x || >=6.0.0"
} }
}, },
"node_modules/@tensorflow/tfjs-node": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-node/-/tfjs-node-4.22.0.tgz",
"integrity": "sha512-uHrXeUlfgkMxTZqHkESSV7zSdKdV0LlsBeblqkuKU9nnfxB1pC6DtoyYVaLxznzZy7WQSegjcohxxCjAf6Dc7w==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "1.0.9",
"@tensorflow/tfjs": "4.22.0",
"adm-zip": "^0.5.2",
"google-protobuf": "^3.9.2",
"https-proxy-agent": "^2.2.1",
"progress": "^2.0.0",
"rimraf": "^2.6.2",
"tar": "^6.2.1"
},
"engines": {
"node": ">=8.11.0"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.9.tgz",
"integrity": "sha512-aDF3S3rK9Q2gey/WAttUlISduDItz5BU3306M9Eyv6/oS40aMprnopshtlKTykxRNIBEZuRMaZAnbrQ4QtKGyw==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/semver": {
"version": "7.8.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
"integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/@mapbox/node-pre-gyp/node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
"dependencies": {
"agent-base": "^4.3.0",
"debug": "^3.1.0"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent/node_modules/agent-base": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
"dependencies": {
"es6-promisify": "^5.0.0"
},
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/https-proxy-agent/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/@tensorflow/tfjs-node/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/@tensorflow/tfjs-node/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/@tensorflow/tfjs-node/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-cpu": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz",
"integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==",
"dependencies": {
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-backend-webgl": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz",
"integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@types/offscreencanvas": "~2019.3.0",
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-converter": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz",
"integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz",
"integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==",
"dependencies": {
"@types/long": "^4.0.1",
"@types/offscreencanvas": "~2019.7.0",
"@types/seedrandom": "^2.4.28",
"@webgpu/types": "0.1.38",
"long": "4.0.0",
"node-fetch": "~2.6.1",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
"version": "2019.7.3",
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A=="
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-data": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz",
"integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==",
"dependencies": {
"@types/node-fetch": "^2.1.2",
"node-fetch": "~2.6.1",
"string_decoder": "^1.3.0"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0",
"seedrandom": "^3.0.5"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@tensorflow/tfjs-layers": {
"version": "4.22.0",
"resolved": "https://registry.npmjs.org/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz",
"integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/@types/seedrandom": {
"version": "2.4.34",
"resolved": "https://registry.npmjs.org/@types/seedrandom/-/seedrandom-2.4.34.tgz",
"integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A=="
},
"node_modules/@tensorflow/tfjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@tensorflow/tfjs/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@tensorflow/tfjs/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@tensorflow/tfjs/node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/@tensorflow/tfjs/node_modules/core-js": {
"version": "3.29.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz",
"integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==",
"hasInstallScript": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/@tensorflow/tfjs/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/@tensorflow/tfjs/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
},
"node_modules/@tensorflow/tfjs/node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="
},
"node_modules/@tensorflow/tfjs/node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
},
"node_modules/@tensorflow/tfjs/node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@tensorflow/tfjs/node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/@tensorflow/tfjs/node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"node_modules/@tensorflow/tfjs/node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@tensorflow/tfjs/node_modules/yargs": {
"version": "16.2.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.2.tgz",
"integrity": "sha512-Nt9ZJjXTv5R8MHbqby/wXQ6Gi0Bb3TcYZkR1bzuL4yB2OxWPkXknz513gEF0GoA6tn00UpbPvERW8rzCuWCA6w==",
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@tokenizer/token": { "node_modules/@tokenizer/token": {
"version": "0.3.0", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
@@ -5180,6 +5717,11 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA=="
},
"node_modules/@types/minimist": { "node_modules/@types/minimist": {
"version": "1.2.5", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz", "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
@@ -5199,6 +5741,15 @@
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
} }
}, },
"node_modules/@types/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.4"
}
},
"node_modules/@types/normalize-package-data": { "node_modules/@types/normalize-package-data": {
"version": "2.4.4", "version": "2.4.4",
"resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
@@ -5446,6 +5997,15 @@
"is-function": "^1.0.1" "is-function": "^1.0.1"
} }
}, },
"node_modules/@vladmandic/human": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@vladmandic/human/-/human-3.3.6.tgz",
"integrity": "sha512-Nr5mPfq1gQ+uKeXY5uM3Fj8UxvF5CEh2s6txM5wRThqaW0mF9huooOuZSrT8hhGho0hGaXLFdAwfD/2+teCv6A==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@vue/compiler-core": { "node_modules/@vue/compiler-core": {
"version": "3.5.25", "version": "3.5.25",
"resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.25.tgz",
@@ -5688,6 +6248,11 @@
"@xtuc/long": "4.2.2" "@xtuc/long": "4.2.2"
} }
}, },
"node_modules/@webgpu/types": {
"version": "0.1.38",
"resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.38.tgz",
"integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA=="
},
"node_modules/@webpack-cli/configtest": { "node_modules/@webpack-cli/configtest": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-2.1.1.tgz",
@@ -5831,6 +6396,14 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/adm-zip": {
"version": "0.5.18",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.18.tgz",
"integrity": "sha512-ufJnssQGbxzLNS1Ho9bCtX4rQKCCvoVuDLHoJyc3F9dOGDB4BkWs2Ci0kv53lqocAEQ/Cbi+I2XCsNYGqVYqng==",
"engines": {
"node": ">=12.0"
}
},
"node_modules/aes-decrypter": { "node_modules/aes-decrypter": {
"version": "4.0.1", "version": "4.0.1",
"resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz", "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-4.0.1.tgz",
@@ -7209,6 +7782,8 @@
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true, "hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0", "@mapbox/node-pre-gyp": "^1.0.0",
"nan": "^2.17.0", "nan": "^2.17.0",
@@ -8249,6 +8824,8 @@
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"mimic-response": "^2.0.0" "mimic-response": "^2.0.0"
}, },
@@ -8815,13 +9392,14 @@
} }
}, },
"node_modules/es-set-tostringtag": { "node_modules/es-set-tostringtag": {
"version": "2.0.2", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.2.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dependencies": { "dependencies": {
"get-intrinsic": "^1.2.2", "es-errors": "^1.3.0",
"has-tostringtag": "^1.0.0", "get-intrinsic": "^1.2.6",
"hasown": "^2.0.0" "has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -8851,6 +9429,19 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/es6-promise": {
"version": "4.2.8",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
},
"node_modules/es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==",
"dependencies": {
"es6-promise": "^4.0.3"
}
},
"node_modules/escalade": { "node_modules/escalade": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
@@ -10250,13 +10841,15 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.0", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==",
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"mime-types": "^2.1.12" "es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.4",
"mime-types": "^2.1.35"
}, },
"engines": { "engines": {
"node": ">= 6" "node": ">= 6"
@@ -10733,6 +11326,11 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/google-protobuf": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz",
"integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ=="
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
@@ -10984,11 +11582,11 @@
} }
}, },
"node_modules/has-tostringtag": { "node_modules/has-tostringtag": {
"version": "1.0.0", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.2" "has-symbols": "^1.0.3"
}, },
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@@ -11009,9 +11607,9 @@
"dev": true "dev": true
}, },
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
}, },
@@ -13030,6 +13628,11 @@
"node": ">= 12.0.0" "node": ">= 12.0.0"
} }
}, },
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"node_modules/longjohn": { "node_modules/longjohn": {
"version": "0.2.12", "version": "0.2.12",
"resolved": "https://registry.npmjs.org/longjohn/-/longjohn-0.2.12.tgz", "resolved": "https://registry.npmjs.org/longjohn/-/longjohn-0.2.12.tgz",
@@ -13422,6 +14025,8 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
"optional": true,
"peer": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
}, },
@@ -19475,12 +20080,16 @@
"type": "consulting", "type": "consulting",
"url": "https://feross.org/support" "url": "https://feross.org/support"
} }
] ],
"optional": true,
"peer": true
}, },
"node_modules/simple-get": { "node_modules/simple-get": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz", "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==", "integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"optional": true,
"peer": true,
"dependencies": { "dependencies": {
"decompress-response": "^4.2.0", "decompress-response": "^4.2.0",
"once": "^1.3.1", "once": "^1.3.1",
@@ -20130,9 +20739,10 @@
} }
}, },
"node_modules/tar": { "node_modules/tar": {
"version": "6.2.0", "version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.0.tgz", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dependencies": { "dependencies": {
"chownr": "^2.0.0", "chownr": "^2.0.0",
"fs-minipass": "^2.0.0", "fs-minipass": "^2.0.0",
@@ -22729,7 +23339,6 @@
"version": "20.2.9", "version": "20.2.9",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"dev": true,
"engines": { "engines": {
"node": ">=10" "node": ">=10"
} }

View File

@@ -1,6 +1,6 @@
{ {
"name": "traxxx", "name": "traxxx",
"version": "1.253.2", "version": "1.254.15",
"description": "All the latest porn releases in one place", "description": "All the latest porn releases in one place",
"main": "src/app.js", "main": "src/app.js",
"scripts": { "scripts": {
@@ -76,6 +76,9 @@
"@graphile-contrib/pg-order-by-related": "^1.0.0", "@graphile-contrib/pg-order-by-related": "^1.0.0",
"@graphile-contrib/pg-simplify-inflector": "^6.1.0", "@graphile-contrib/pg-simplify-inflector": "^6.1.0",
"@graphile/pg-aggregates": "^0.1.1", "@graphile/pg-aggregates": "^0.1.1",
"@rsc-parser/react-client": "^1.1.2",
"@tensorflow/tfjs-node": "^4.22.0",
"@vladmandic/human": "^3.3.6",
"acorn": "^8.11.2", "acorn": "^8.11.2",
"array-equal": "^1.0.2", "array-equal": "^1.0.2",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
@@ -84,7 +87,6 @@
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"bottleneck": "^2.19.5", "bottleneck": "^2.19.5",
"canvas": "^2.11.2",
"casual": "^1.6.2", "casual": "^1.6.2",
"cheerio": "^1.0.0-rc.12", "cheerio": "^1.0.0-rc.12",
"cli-confirm": "^1.0.1", "cli-confirm": "^1.0.1",

View File

@@ -95,6 +95,14 @@ const tags = [
slug: '69', slug: '69',
group: 'position', group: 'position',
}, },
{
name: 'AI generated',
slug: 'ai',
},
{
name: 'AI enhanced',
slug: 'aienhanced',
},
{ {
name: 'airtight', name: 'airtight',
slug: 'airtight', slug: 'airtight',
@@ -114,6 +122,7 @@ const tags = [
{ {
name: 'anal', name: 'anal',
slug: 'anal', slug: 'anal',
rename: 'ass-fucking',
description: 'Getting your asshole fucked. Generally considered naughtier, you may or may not find it a pleasurable alternative to vaginal sex.', description: 'Getting your asshole fucked. Generally considered naughtier, you may or may not find it a pleasurable alternative to vaginal sex.',
group: 'penetration', group: 'penetration',
}, },
@@ -160,8 +169,19 @@ const tags = [
name: 'ass to other girl\'s mouth', name: 'ass to other girl\'s mouth',
slug: 'atogm', slug: 'atogm',
description: '[Ass to mouth](/tag/atm) with a cock that has been in someone else\'s ass. ATOGM may also be the gay variation "ass to other guy\'s mouth".', description: '[Ass to mouth](/tag/atm) with a cock that has been in someone else\'s ass. ATOGM may also be the gay variation "ass to other guy\'s mouth".',
implies: ['atm'],
group: 'oral', group: 'oral',
}, },
{
name: 'ass to pussy',
slug: 'atp',
description: 'Getting your pussy fucked right after it\'s been in your ass, or [someone else\'s](/tag/atogp).',
},
{
name: 'ass to other girl\'s pussy',
slug: 'atogp',
description: 'Getting your pussy fucked right after it\'s been in someone else\'s ass.',
},
{ {
name: 'ass eating', name: 'ass eating',
slug: 'ass-eating', slug: 'ass-eating',
@@ -311,6 +331,10 @@ const tags = [
name: 'bondage', name: 'bondage',
slug: 'bondage', slug: 'bondage',
}, },
{
name: 'bimbo',
slug: 'bimbo',
},
{ {
name: 'braces', name: 'braces',
slug: 'braces', slug: 'braces',
@@ -544,8 +568,10 @@ const tags = [
slug: 'electric-shock', slug: 'electric-shock',
}, },
{ {
name: 'enhanced boobs', name: 'fake tits',
slug: 'enhanced-boobs', slug: 'fake-tits',
description: 'Tasteful enhancements or bimbo bolt-ons, you don\'t mind a bit of plastic to get the boobs you\'ve always wanted.',
rename: 'enhanced-boobs',
}, },
{ {
name: 'enhanced butt', name: 'enhanced butt',
@@ -736,6 +762,11 @@ const tags = [
slug: 'high-heels', slug: 'high-heels',
group: 'clothing', group: 'clothing',
}, },
{
name: 'hotel',
slug: 'hotel',
group: 'location',
},
{ {
name: 'humiliation', name: 'humiliation',
slug: 'humiliation', slug: 'humiliation',
@@ -832,13 +863,18 @@ const tags = [
name: 'MFM threesome', name: 'MFM threesome',
slug: 'mfm', slug: 'mfm',
implies: ['threesome'], implies: ['threesome'],
description: 'Two men fucking one woman, but not eachother. Typically involves a \'spitroast\', where one guy gets a blowjob and the other fucks her pussy or ass.', description: 'Two men fucking one woman, but not eachother. Typically involves a [spitroast](/tag/spitroast), where one guy gets a blowjob and the other fucks her pussy or ass.',
group: 'group', group: 'group',
}, },
{ {
name: 'spitroast', name: 'spitroast',
slug: 'spitroast', slug: 'spitroast',
}, },
{
name: 'anal spitroast',
slug: 'anal-spitroast',
implies: ['anal', 'spitroast'],
},
{ {
name: 'military', name: 'military',
slug: 'military', slug: 'military',
@@ -858,8 +894,9 @@ const tags = [
group: 'position', group: 'position',
}, },
{ {
name: 'natural boobs', name: 'natural tits',
slug: 'natural-boobs', slug: 'natural-tits',
rename: 'natural-boobs',
group: 'body', group: 'body',
}, },
{ {
@@ -1227,6 +1264,10 @@ const tags = [
slug: 'toy-dp', slug: 'toy-dp',
description: 'Getting [double penetrated](/tag/dp) with dildos, strap-ons or buttplugs. You can use both ends of a [double dildo](/tag/double-dildo) for a [double dildo DP](/tag/double-dildo-dp).', description: 'Getting [double penetrated](/tag/dp) with dildos, strap-ons or buttplugs. You can use both ends of a [double dildo](/tag/double-dildo) for a [double dildo DP](/tag/double-dildo-dp).',
}, },
{
name: 'finger DP',
slug: 'finger-dp',
},
{ {
name: 'transsexual', name: 'transsexual',
slug: 'transsexual', slug: 'transsexual',
@@ -1641,15 +1682,18 @@ const aliases = [
name: 'big butts', name: 'big butts',
for: 'big-butt', for: 'big-butt',
}, },
{
name: 'natural boobs',
for: 'natural-tits',
slug: 'natural-boobs',
},
{ {
name: 'big natural tits', name: 'big natural tits',
for: 'big-boobs', for: 'big-boobs',
implies: ['natural-boobs'],
}, },
{ {
name: 'big natural boobs', name: 'big natural boobs',
for: 'big-boobs', for: 'big-boobs',
implies: ['natural-boobs'],
}, },
{ {
name: 'big tits', name: 'big tits',
@@ -1729,12 +1773,18 @@ const aliases = [
}, },
{ {
name: 'fake boobs', name: 'fake boobs',
for: 'enhanced-boobs', for: 'fake-tits',
secondary: true,
},
{
name: 'enhanced boobs',
for: 'fake-tits',
slug: 'enhanced-boobs',
secondary: true, secondary: true,
}, },
{ {
name: 'implants', name: 'implants',
for: 'enhanced-boobs', for: 'fake-tits',
}, },
{ {
name: 'boob job', name: 'boob job',
@@ -1742,7 +1792,7 @@ const aliases = [
}, },
{ {
name: 'boobjob', name: 'boobjob',
for: 'enhanced-boobs', for: 'fake-tits',
}, },
{ {
name: 'brown hair', name: 'brown hair',
@@ -2007,11 +2057,11 @@ const aliases = [
}, },
{ {
name: 'enhanced', name: 'enhanced',
for: 'enhanced-boobs', for: 'fake-tits',
}, },
{ {
name: 'enhanced tits', name: 'enhanced tits',
for: 'enhanced-boobs', for: 'fake-tits',
}, },
{ {
name: 'fake butt', name: 'fake butt',
@@ -2054,11 +2104,6 @@ const aliases = [
name: 'facial - multiple', name: 'facial - multiple',
for: 'facial', for: 'facial',
}, },
{
name: 'fake tits',
for: 'enhanced-boobs',
secondary: true,
},
{ {
name: 'feet sex', name: 'feet sex',
for: 'foot-sex', for: 'foot-sex',
@@ -2208,11 +2253,7 @@ const aliases = [
}, },
{ {
name: 'natural', name: 'natural',
for: 'natural-boobs', for: 'natural-tits',
},
{
name: 'natural tits',
for: 'natural-boobs',
}, },
{ {
name: 'natural butt', name: 'natural butt',
@@ -3097,17 +3138,19 @@ const aliases = [
]; ];
const priorities = [ // higher index is higher priority const priorities = [ // higher index is higher priority
['double-dildo', 'double-dildo-blowjob', 'double-dildo-kiss', 'double-dildo-anal', 'double-dildo-dp'], ['blonde', 'brunette', 'black-hair', 'redhead'],
['toys', 'toy-anal', 'toy-dp', 'piss-drinking'],
['family'],
['blowjob', 'deepthroat', 'oil'],
['asian', 'black', 'latina', 'white', 'interracial'], ['asian', 'black', 'latina', 'white', 'interracial'],
['fake-tits', 'natural-tits'],
['bts'], ['bts'],
['blowjob', 'deepthroat', 'oil'],
['toys', 'toy-anal', 'toy-dp', 'piss-drinking'],
['double-dildo', 'double-dildo-blowjob', 'double-dildo-kiss', 'double-dildo-anal', 'double-dildo-dp'],
['family'],
['facefucking', 'gaping', 'atm', 'atogm', 'pussy-to-mouth', 'ass-eating'], ['facefucking', 'gaping', 'atm', 'atogm', 'pussy-to-mouth', 'ass-eating'],
['facial', 'swallowing', 'creampie', 'anal-creampie', 'oral-creampie', 'cum-in-mouth', 'throatpie'], ['facial', 'swallowing', 'creampie', 'anal-creampie', 'oral-creampie', 'cum-in-mouth', 'throatpie'],
['lesbian', 'rough', 'milf', 'male-focus', 'bdsm', 'oil'], ['lesbian', 'rough', 'milf', 'male-focus', 'bdsm', 'oil'],
['threesome', 'mfm', 'mff', 'trainbang', 'pissing'], ['threesome', 'mfm', 'mff', 'trainbang', 'pissing'],
['anal', 'bukkake', 'spitroast'], ['anal', 'bukkake', 'spitroast', 'anal-spitroast'],
['dp', 'dap', 'triple-penetration', 'tap', 'dvp', 'tvp', 'airtight'], ['dp', 'dap', 'triple-penetration', 'tap', 'dvp', 'tvp', 'airtight'],
['blowbang', 'orgy'], ['blowbang', 'orgy'],
['gangbang'], ['gangbang'],
@@ -3123,6 +3166,33 @@ const priorities = [ // higher index is higher priority
exports.seed = (knex) => Promise.resolve() exports.seed = (knex) => Promise.resolve()
.then(async () => upsert('tags_groups', groups, 'slug', knex)) .then(async () => upsert('tags_groups', groups, 'slug', knex))
.then(async () => { .then(async () => {
await Promise.all(tags.map((async (tag) => {
if (tag.rename) {
await knex('tags')
.where((builder) => {
builder
.where('slug', tag.slug)
.orWhere('name', tag.name);
})
.whereNotNull('alias_for')
.delete();
await knex('tags')
.where('slug', tag.rename)
.whereNull('alias_for')
.update({
name: tag.name,
slug: tag.slug,
});
}
if (tag.delete) {
await knex('tags')
.where('slug', tag.rename)
.delete();
}
})));
const groupEntries = await knex('tags_groups').select('*'); const groupEntries = await knex('tags_groups').select('*');
const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {}); const groupsMap = groupEntries.reduce((acc, { id, slug }) => ({ ...acc, [slug]: id }), {});
@@ -3170,6 +3240,7 @@ exports.seed = (knex) => Promise.resolve()
return { return {
name: alias.name, name: alias.name,
slug: alias.slug, // only used if redirect is required
alias_for: tagsMap[alias.for], alias_for: tagsMap[alias.for],
implied_tag_ids: alias.implies?.map((slug) => tagsMap[slug]), implied_tag_ids: alias.implies?.map((slug) => tagsMap[slug]),
secondary: !!alias.secondary, secondary: !!alias.secondary,

View File

@@ -621,6 +621,11 @@ const networks = [
url: 'https://mamacitaz.com', url: 'https://mamacitaz.com',
parent: 'porndoe', parent: 'porndoe',
}, },
{
slug: 'manyvids',
name: 'ManyVids',
url: 'https://www.manyvids.com',
},
{ {
slug: 'men', slug: 'men',
name: 'Men', name: 'Men',

View File

@@ -6135,6 +6135,12 @@ const sites = [
scene: false, scene: false,
}, },
}, },
// JAX SLAYHER
{
slug: 'jaxslayher',
name: 'Jax Slayher',
url: 'https://jaxslayher.com',
},
// JESSE LOADS MONSTER FACIALS // JESSE LOADS MONSTER FACIALS
{ {
slug: 'jesseloadsmonsterfacials', slug: 'jesseloadsmonsterfacials',
@@ -7685,6 +7691,15 @@ const sites = [
delete: true, delete: true,
parent: 'mamacitaz', parent: 'mamacitaz',
}, },
// MANYVIDS
{
name: 'nicoledoshi',
slug: 'mv.nicoledoshi',
url: 'https://nicoledoshi.manyvids.com',
hasLogo: false,
parent: 'manyvids',
delete: true,
},
// MARISKA X // MARISKA X
{ {
name: 'MariskaX', name: 'MariskaX',
@@ -9471,6 +9486,12 @@ const sites = [
url: 'https://shesbreedingmaterial.com', url: 'https://shesbreedingmaterial.com',
parent: 'nubiles', parent: 'nubiles',
}, },
{
slug: 'glowingdesire',
name: 'Glowing Desire',
url: 'https://glowingdesire.com',
parent: 'nubiles',
},
// PASCALS SUBSLUTS // PASCALS SUBSLUTS
{ {
slug: 'pascalssubsluts', slug: 'pascalssubsluts',
@@ -10329,6 +10350,12 @@ const sites = [
url: 'https://pornplus.com/series/pimpparade', url: 'https://pornplus.com/series/pimpparade',
parent: 'pornplus', parent: 'pornplus',
}, },
{
name: 'Penis To Pussy Marketplace',
slug: 'p2pmarketplace',
url: 'https://pornplus.com/series/p2p-marketplace',
parent: 'pornplus',
},
{ {
name: 'Porn Pros', name: 'Porn Pros',
slug: 'pornprosplus', slug: 'pornprosplus',

View File

@@ -1144,7 +1144,7 @@ exports.seed = (knex) => Promise.resolve()
entity_id: entitiesBySlug[media.entitySlug]?.id, entity_id: entitiesBySlug[media.entitySlug]?.id,
})), 'path', knex); })), 'path', knex);
const tagIdsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.id }), {}); const tagIdsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag.alias_for || tag.id }), {});
const mediaIdsByPath = inserted.concat(updated).reduce((acc, item) => ({ ...acc, [item.path]: item.id }), {}); const mediaIdsByPath = inserted.concat(updated).reduce((acc, item) => ({ ...acc, [item.path]: item.id }), {});
const tagMediaBySlug = tagMedia.reduce((acc, tagPhoto) => ({ const tagMediaBySlug = tagMedia.reduce((acc, tagPhoto) => ({

28
seeds/08_abilities.js Normal file
View File

@@ -0,0 +1,28 @@
exports.seed = async (knex) => {
await knex('users_roles')
.update('abilities', JSON.stringify([
{ subject: 'scene', action: 'create' },
{ subject: 'scene', action: 'update' },
{ subject: 'scene', action: 'delete' },
{ subject: 'actor', action: 'create' },
{ subject: 'actor', action: 'update' },
{ subject: 'actor', action: 'delete' },
{ subject: 'actor', action: 'merge' },
{ subject: 'sync' },
{ subject: 'plainUrls' },
]))
.where('role', 'admin');
await knex('users_roles')
.update('abilities', JSON.stringify([
{ subject: 'scene', action: 'create' },
{ subject: 'scene', action: 'update' },
{ subject: 'scene', action: 'delete' },
{ subject: 'actor', action: 'create' },
{ subject: 'actor', action: 'update' },
{ subject: 'actor', action: 'delete' },
{ subject: 'actor', action: 'merge' },
{ subject: 'plainUrls' },
]))
.where('role', 'editor');
};

58
seeds/09_manyvids.js Normal file
View File

@@ -0,0 +1,58 @@
'use strict';
const unprint = require('unprint');
const slugify = require('../src/utils/slugify');
const limit = Number(process.env.MV_LIMIT) || 1000;
const sorting = process.env.MV_SORT || 'followers'; // followers, top, mostActive, newest, trending
async function fetchCreators(page = 1, acc = []) {
console.log(`Fetching ${acc.length}/${limit} creators by ${sorting}`);
// size seems to be capped at 70
const res = await unprint.get(`https://api.manyvids.com/search/creators/list?contentPref=1,2,3&sort=${sorting}&size=50&from=${(page - 1) * 50}`);
if (res.ok && res.data?.creators) {
const creators = acc.concat(res.data.creators.map((creator) => ({
name: creator.stageName,
slug: `mv.${slugify(creator.slug, '')}`,
url: `https://www.manyvids.com/Profile/${creator.id}/${creator.slug}/`,
emblem: creator.avatar?.url || null,
})));
if (creators.length >= limit) {
return creators;
}
return fetchCreators(page + 1, creators);
}
return acc;
}
exports.seed = async function (knex) {
const channels = await fetchCreators();
const manyvidsNetwork = await knex('entities')
.where('slug', 'manyvids')
.where('type', 'network')
.first();
if (!manyvidsNetwork) {
throw new Error('ManyVids found, did the network migration run?');
}
const result = await knex('entities')
.insert(channels.map((channel) => ({
name: channel.name,
slug: channel.slug,
url: channel.url,
parent_id: manyvidsNetwork.id,
has_logo: false,
})))
.onConflict(['slug', 'type'])
.merge(['name', 'url'])
.returning(knex.raw('(xmax = 0) as inserted'));
console.log(`Done! ${result.filter((row) => row.inserted).length}/${channels.length} ManyVids creators newly added as channels`);
};

View File

@@ -31,6 +31,7 @@ const { associateAvatars, flushOrphanedMedia } = require('./media');
const { fetchEntitiesBySlug } = require('./entities'); const { fetchEntitiesBySlug } = require('./entities');
const { deleteScenes } = require('./releases'); const { deleteScenes } = require('./releases');
const { updateActorSearch } = require('./update-search'); const { updateActorSearch } = require('./update-search');
const { setBiometrics } = require('./biometrics');
const actorsCommon = import('../common/actors.mjs'); // eslint-disable-line import/extensions, import/no-relative-packages const actorsCommon = import('../common/actors.mjs'); // eslint-disable-line import/extensions, import/no-relative-packages
const geoCommon = import('../common/geo.mjs'); // eslint-disable-line import/extensions, import/no-relative-packages const geoCommon = import('../common/geo.mjs'); // eslint-disable-line import/extensions, import/no-relative-packages
@@ -213,6 +214,7 @@ function curateActor(actor, withDetails = false, isProfile = false) {
name: actor.name, name: actor.name,
slug: actor.slug, slug: actor.slug,
url: actor.url, url: actor.url,
entryId: actor.entry_id,
gender: actor.gender, gender: actor.gender,
orientation: actor.orientation, orientation: actor.orientation,
entityId: actor.entity_id, entityId: actor.entity_id,
@@ -692,7 +694,7 @@ async function scrapeProfiles(actor, sources, entitiesBySlug, existingProfilesBy
} }
}), Promise.reject(new Error())); }), Promise.reject(new Error()));
} catch (error) { } catch (error) {
console.log(error); // console.log(error);
if (error.code !== 'PROFILE_NOT_AVAILABLE') { if (error.code !== 'PROFILE_NOT_AVAILABLE') {
logger.error(`Failed to fetch profile for '${actor.name}': ${error.message}`); logger.error(`Failed to fetch profile for '${actor.name}': ${error.message}`);
@@ -824,6 +826,11 @@ async function storeProfiles(profiles) {
await upsertProfiles(profilesWithAvatarIds); await upsertProfiles(profilesWithAvatarIds);
await interpolateProfiles(actorIds); await interpolateProfiles(actorIds);
setBiometrics(Array.from(new Set(profiles.map((profile) => profile.actorId)))).catch((error) => {
// biometrics are courtesy, don't await
logger.error(`Biometrics failed: ${error.message}`);
});
} }
async function scrapeActors(argNames) { async function scrapeActors(argNames) {
@@ -913,7 +920,7 @@ async function getOrCreateActors(baseActors, batchId) {
.whereRaw(` .whereRaw(`
actors.slug = base_actors.slug actors.slug = base_actors.slug
AND actors.entity_id IS NULL AND actors.entity_id IS NULL
AND NOT base_actors.collision_likely AND (NOT base_actors.collision_likely OR actors.allow_global_match)
`) `)
.orWhereRaw(` .orWhereRaw(`
actors.slug = base_actors.slug actors.slug = base_actors.slug
@@ -981,7 +988,7 @@ async function getOrCreateActors(baseActors, batchId) {
await storeProfiles(newActorProfiles); await storeProfiles(newActorProfiles);
if (Array.isArray(newActors)) { if (Array.isArray(newActors) && newActors.length > 0) {
await updateActorSearch(newActors.map((actor) => actor.id)); await updateActorSearch(newActors.map((actor) => actor.id));
return newActors.concat(existingActors); return newActors.concat(existingActors);
} }

213
src/biometrics.js Normal file
View File

@@ -0,0 +1,213 @@
'use strict';
const config = require('config');
const path = require('path');
const fsPromises = require('fs').promises;
const Human = require('@vladmandic/human').default;
const knex = require('./knex');
const logger = require('./logger')(__filename);
const humanConfig = {
modelBasePath: `file://${path.join(__dirname, '../node_modules/@vladmandic/human/models')}/`,
backend: 'tensorflow', // uses tfjs-node under the hood
face: {
enabled: true,
detector: { rotation: false },
mesh: { enabled: false },
iris: { enabled: false },
description: { enabled: true },
emotion: { enabled: true },
},
body: { enabled: false },
hand: { enabled: false },
gesture: { enabled: false },
};
let humanInstance = null;
const nsPerSec = 1e9;
const remainingTimeWindow = 30; // number of recent entries used to estimate time remaining
async function getHumanInstance() {
if (!humanInstance) {
humanInstance = new Human(humanConfig);
await humanInstance.load();
await humanInstance.warmup();
}
return humanInstance;
}
async function getImageBuffer(mediaEntry) {
if (mediaEntry.is_s3) {
if (!config.s3.enabled) {
logger.verbose(`Skipping biometrics for ${mediaEntry.media_id}, S3 not enabled`);
return null;
}
const res = await fetch(`https://${config.s3.bucket}/${mediaEntry.path}`);
if (res.ok) {
return Buffer.from(await res.arrayBuffer());
}
return null;
}
try {
const buffer = await fsPromises.readFile(path.join(config.media.path, mediaEntry.path));
return buffer;
} catch (_error) {
return null;
}
}
async function getBiometrics(avatarEntry) {
if (!avatarEntry) {
return null;
}
const human = await getHumanInstance();
const buffer = await getImageBuffer(avatarEntry);
if (!buffer) {
return null;
}
let tensor;
try {
tensor = human.tf.node.decodeImage(buffer, 3);
const result = await human.detect(tensor);
return result;
} catch (error) {
logger.error(`Failed to decode or detect ${avatarEntry.media_id}: ${error.message}`);
return null;
} finally {
tensor?.dispose();
}
}
function getAvatarSource(actorIds, includePhotos) {
if (includePhotos) {
return knex('actors_avatars')
.select('media_id')
.modify((builder) => {
if (actorIds) {
builder.whereIn('actor_id', actorIds);
}
});
}
return knex('actors')
.select('avatar_media_id as media_id')
.whereNotNull('avatar_media_id')
.modify((builder) => {
if (actorIds) {
builder.whereIn('id', actorIds);
}
});
}
async function setBiometrics(actorIds, shouldUpdate = false, includePhotos = false) {
if (!config.media.biometrics.enabled) {
return;
}
const startTime = process.hrtime();
const recentDurationsMs = [];
const avatarSource = getAvatarSource(actorIds, includePhotos);
const avatarEntries = await knex('media')
.select(
'media.id as media_id',
'media.path',
'media.is_s3',
knex.raw('coalesce(json_agg(media_biometrics.biometrics) filter (where media_biometrics.id is not null), \'[]\') as biometrics'),
)
.leftJoin('media_biometrics', 'media_biometrics.media_id', 'media.id')
.whereIn('media.id', avatarSource)
.groupBy('media.id', 'media.path', 'media.is_s3');
await avatarEntries.reduce(async (chain, avatarEntry, avatarIndex) => {
await chain;
if (avatarEntry.biometrics.length > 0 && !shouldUpdate) {
logger.verbose(`Skipping biometrics for ${avatarEntry.media_id}, already present`);
return;
}
const entryStartTime = process.hrtime();
const biometrics = await getBiometrics(avatarEntry);
if (!biometrics) {
return;
}
const curatedBiometrics = biometrics.face.map((face) => ({
biometrics: {
age: face.age,
gender: face.gender,
genderScore: face.genderScore,
emotion: face.emotion,
box: face.box,
score: face.score,
...Object.fromEntries(Object.entries(face.annotations).map(([key, annotation]) => [key, annotation.filter(Boolean)[0] || null])),
},
embedding: face.embedding,
}));
if (curatedBiometrics.length === 0) {
return;
}
if (shouldUpdate) {
await knex('media_biometrics')
.where('media_id', avatarEntry.media_id)
.delete();
}
await knex('media_biometrics')
.insert(curatedBiometrics.map((face, index) => ({
media_id: avatarEntry.media_id,
face_index: index,
width: biometrics.width,
height: biometrics.height,
biometrics: JSON.stringify(face.biometrics),
embedding: knex.raw('?::vector', [JSON.stringify(face.embedding)]),
updated_at: knex.raw('now()'),
})))
.onConflict(['media_id', 'face_index'])
.ignore();
const [entrySec, entryNs] = process.hrtime(entryStartTime);
const entryMs = (entrySec * nsPerSec + entryNs) / 1e6;
recentDurationsMs.push(entryMs);
if (recentDurationsMs.length > remainingTimeWindow) {
recentDurationsMs.shift();
}
const avgMsPerEntry = recentDurationsMs.reduce((sum, ms) => sum + ms, 0) / recentDurationsMs.length;
const remainingMs = avgMsPerEntry * (avatarEntries.length - (avatarIndex + 1));
const remainingMinutes = (remainingMs / 1000 / 60).toFixed(1);
logger.info(`Set biometrics for ${avatarEntry.media_id} (${avatarIndex + 1}/${avatarEntries.length}, ~${remainingMinutes}m remaining)`);
}, Promise.resolve());
const diffTime = process.hrtime(startTime);
const totalMs = (diffTime[0] * nsPerSec + diffTime[1]) / 1e6;
logger.info(`Biometrics done in ${(totalMs / 1000).toFixed(1)}s for ${actorIds?.length || 'all'} actors and ${avatarEntries.length} avatars`);
}
module.exports = {
setBiometrics,
};

View File

@@ -28,7 +28,6 @@ const chunk = require('./utils/chunk');
const { get } = require('./utils/qu'); const { get } = require('./utils/qu');
const { fetchEntityReleaseIds } = require('./entity-releases'); const { fetchEntityReleaseIds } = require('./entity-releases');
// const pipeline = util.promisify(stream.pipeline);
const streamQueue = taskQueue(); const streamQueue = taskQueue();
const s3 = new S3Client({ const s3 = new S3Client({
@@ -516,16 +515,6 @@ async function storeImageFile(media, hashDir, hashSubDir, filename, filedir, fil
writeLazy(image, lazypath, info), writeLazy(image, lazypath, info),
]); ]);
/*
if (isProcessed) {
// file already stored, remove temporary file
await fsPromises.unlink(media.file.path);
} else {
// image not processed, simply move temporary file to final location
await fsPromises.rename(media.file.path, path.join(config.media.path, filepath));
}
*/
await fsPromises.unlink(media.file.path); await fsPromises.unlink(media.file.path);
if (config.s3.enabled) { if (config.s3.enabled) {
@@ -731,31 +720,31 @@ async function fetchSource(source, baseMedia) {
} }
async function attempt(attempts = 1) { async function attempt(attempts = 1) {
const hasher = new blake2.Hash('blake2b', { digestLength: 24 });
let hasherReady = true;
hasher.setEncoding('hex');
try {
const tempFilePath = path.join(config.media.path, 'temp', `${baseMedia.id}`); const tempFilePath = path.join(config.media.path, 'temp', `${baseMedia.id}`);
const tempFileTarget = fs.createWriteStream(tempFilePath); let tempFileTarget;
const hashStream = new stream.PassThrough();
const hasher = blake2.createHash('blake2b', { digestLength: 24 });
let size = 0; let size = 0;
hashStream.on('data', (streamChunk) => { const hashStream = new stream.Transform({
transform(streamChunk, _encoding, callback) {
size += streamChunk.length; size += streamChunk.length;
hasher.update(streamChunk);
if (hasherReady) { this.push(streamChunk);
hasher.write(streamChunk); callback();
} },
}); });
try {
tempFileTarget = fs.createWriteStream(tempFilePath);
const { mimetype } = source.stream const { mimetype } = source.stream
? await streamQueue.push('fetchStreamSource', { source, tempFileTarget, hashStream }) ? await streamQueue.push('fetchStreamSource', { source, tempFileTarget, hashStream })
: await fetchHttpSource(source, tempFileTarget, hashStream); : await fetchHttpSource(source, tempFileTarget, hashStream);
hasher.end(); const hash = hasher.digest('hex');
const hash = hasher.read();
const [type, subtype] = mimetype.split('/'); const [type, subtype] = mimetype.split('/');
const extension = mime.getExtension(mimetype); const extension = mime.getExtension(mimetype);
@@ -778,8 +767,10 @@ async function fetchSource(source, baseMedia) {
}, },
}; };
} catch (error) { } catch (error) {
hasherReady = false; // hasherReady = false;
hasher.end(); // hasher.end();
hashStream.destroy();
tempFileTarget?.destroy();
if (error.code !== 'VERIFY_TYPE') { if (error.code !== 'VERIFY_TYPE') {
logger.warn(`Failed attempt ${attempts}/${maxAttempts} to fetch ${source.src}: ${error.message}`); logger.warn(`Failed attempt ${attempts}/${maxAttempts} to fetch ${source.src}: ${error.message}`);

View File

@@ -23,6 +23,7 @@ const hitzefrei = require('./hitzefrei');
const hookuphotshot = require('./hookuphotshot'); const hookuphotshot = require('./hookuphotshot');
const hush = require('./hush'); const hush = require('./hush');
const inthecrack = require('./inthecrack'); const inthecrack = require('./inthecrack');
const jaxslayher = require('./jaxslayher');
const julesjordan = require('./julesjordan'); const julesjordan = require('./julesjordan');
const karups = require('./karups'); const karups = require('./karups');
const kellymadison = require('./kellymadison'); const kellymadison = require('./kellymadison');
@@ -33,6 +34,7 @@ const pornbox = require('./pornbox');
const littlecapricedreams = require('./littlecapricedreams'); const littlecapricedreams = require('./littlecapricedreams');
const loveherfilms = require('./loveherfilms'); const loveherfilms = require('./loveherfilms');
const bluedonkeymedia = require('./bluedonkeymedia'); const bluedonkeymedia = require('./bluedonkeymedia');
const manyvids = require('./manyvids');
const mikeadriano = require('./mikeadriano'); const mikeadriano = require('./mikeadriano');
const aylo = require('./aylo'); const aylo = require('./aylo');
const missax = require('./missax'); const missax = require('./missax');
@@ -205,14 +207,6 @@ module.exports = {
jerkaoke: modelmedia, jerkaoke: modelmedia,
modelmediaasia: modelmedia, modelmediaasia: modelmedia,
delphine: modelmedia, delphine: modelmedia,
// etc
'18vr': badoink,
theflourishxxx: theflourish,
pierrewoodman,
exploitedx, // only from known URL that will specify site
fullpornnetwork,
adultempire,
allherluv: missax,
// americanpornstar, // americanpornstar,
angelogodshackoriginal, angelogodshackoriginal,
babevr: badoink, babevr: badoink,
@@ -230,6 +224,7 @@ module.exports = {
hitzefrei, hitzefrei,
hookuphotshot, hookuphotshot,
inthecrack, inthecrack,
jaxslayher,
karups, karups,
boyfun: karups, boyfun: karups,
kellymadison, kellymadison,
@@ -281,4 +276,13 @@ module.exports = {
slayed: vixen, slayed: vixen,
wifey: vixen, wifey: vixen,
vrcosplayx: badoink, vrcosplayx: badoink,
// etc
'18vr': badoink,
theflourishxxx: theflourish,
pierrewoodman,
exploitedx, // only from known URL that will specify site
fullpornnetwork,
adultempire,
allherluv: missax,
manyvids,
}; };

257
src/scrapers/jaxslayher.js Executable file
View File

@@ -0,0 +1,257 @@
'use strict';
const unprint = require('unprint');
const slugify = require('../utils/slugify');
const { convert } = require('../utils/convert');
const tryUrls = require('../utils/try-urls');
// parse Next/React data tree
async function parsePayload(rawText) {
const { createFlightResponse, processBinaryChunk } = await import('@rsc-parser/react-client'); // eslint-disable-line import/no-unresolved
const response = createFlightResponse(false);
const bytes = new TextEncoder().encode(rawText);
processBinaryChunk(response, bytes);
return response._chunks;
}
function traverseData(node, predicate, resultDepth = 3, seen = new WeakSet(), ancestors = []) {
if (node === null || typeof node !== 'object') {
return null;
}
if (seen.has(node)) {
return null;
}
seen.add(node);
if (predicate(node)) {
if (resultDepth === 0) {
return node;
}
const index = ancestors.length - resultDepth;
return ancestors[Math.max(index, 0)] ?? node;
}
const values = Array.isArray(node) ? node : Object.values(node);
const nextAncestors = [...ancestors, node];
let result = null;
values.some((value) => {
result = traverseData(value, predicate, resultDepth, seen, nextAncestors);
return result !== null;
});
return result;
}
function getPoster(videoId, channel) {
if (!videoId) {
return null;
}
return [
`${channel.origin}/images/updates/${videoId}-1280.jpg`,
`${channel.origin}/images/updates/${videoId}-720.jpg`,
`${channel.origin}/images/updates/${videoId}.jpg`, // 502px
`${channel.origin}/images/updates/${videoId}-360.jpg`,
];
}
function getVideo(videoId, path = '/trailers', channel) {
if (!videoId) {
return null;
}
return [
`${channel.origin}${path}/${videoId}.mp4`,
`${channel.origin}${path}/${videoId}-med.mp4`,
];
}
function scrapeAll(scenes, channel) {
return scenes.map((data) => {
const release = {};
release.url = `${channel.origin}/tour/video/${data.slug}`;
release.entryId = data.id; // URL slugs are not unique, some pages on original site unreachable!
release.attributes = {
dataEntryId: data.id, // not used in URL, store as secondary
slug: data.slug,
};
release.title = data.title;
release.date = new Date(data.release_date);
release.actors = data.models.map((model) => ({
name: model.name,
url: `${channel.origin}/tour/model/${slugify(model.slug)}`, // for some reason they are underscored, while URL expects dashes
}));
release.tags = data.tags.map((tag) => tag.tag_name);
release.poster = getPoster(data.id, channel);
release.teaser = getVideo(data.id, '/video_thumbs', channel);
release.trailer = getVideo(data.id, '/trailers', channel);
return release;
});
}
async function fetchLatest(channel, page = 1) {
// HTML does not contain tags, hence we jump through hoops to fetch the data directly
const res = await unprint.get(`${channel.origin}/tour/videos/most-recent/page/${page}.txt`, {
headers: {
rsc: 1,
},
});
if (res.ok) {
const payload = await parsePayload(res.body);
const sceneData = traverseData(payload, (item) => item.content_type === 'video');
if (sceneData) {
return scrapeAll(sceneData.map((item) => item.props.item), channel);
}
}
return res.status;
}
function scrapeScene(payload, { url, channel, baseRelease }) {
const release = {};
const sceneData = traverseData(payload, (item) => Object.hasOwn(item, 'videoID'), 2);
const durationData = traverseData(sceneData, (item) => /\d{2}:\d{2}:\d{2}/.test(item.children), 0);
const videoData = traverseData(sceneData, (item) => Object.hasOwn(item, 'videoID'), 0);
const dateData = traverseData(sceneData, (item) => item.children?.includes?.('2026'), 0);
const videoId = videoData?.videoID;
release.duration = unprint.extractDuration(durationData?.children);
if (baseRelease?.entryId) {
// base release data is much more concise, duration only thing missing
return release;
}
release.entryId = videoId;
release.attributes = {
dataEntryId: videoId,
slug: new URL(url).pathname.match(/\/video\/([\w-]+)/)?.[1],
};
release.title = videoData?.title;
release.date = unprint.extractDate(dateData.children, 'MMM DD/YYYY', { match: null });
release.poster = getPoster(videoId, channel);
release.teaser = getVideo(videoId, '/video_thumbs', channel);
release.trailer = getVideo(videoId, '/trailers', channel);
return release;
}
async function fetchScene(url, channel, baseRelease) {
const dataUrl = `${url.replace(/\/*$/, '')}.txt`;
const res = await unprint.get(dataUrl, {
headers: {
rsc: 1,
},
});
if (res.ok) {
const payload = await parsePayload(res.body);
return scrapeScene(payload, { url, channel, baseRelease });
}
return res.status;
}
function getBioItem(bioData, key) {
if (!bioData) {
return null;
}
const data = traverseData(bioData, (item) => typeof item.children === 'string' && item.children.toLowerCase() === `${key}:`, 2);
const values = data[1]?.props.children;
const value = Array.isArray(values)
? values.find((itemValue) => (typeof itemValue === 'string' ? itemValue.trim() : itemValue))
: values;
if (value && String(value).toLowerCase() !== 'n/a') {
return value;
}
return null;
}
function scrapeProfile(payload, actorName, channel, url) {
const profile = { url };
const actorData = traverseData(payload, (item) => slugify(item.name) === slugify(actorName), 0);
const bioData = traverseData(payload, (item) => item.className === 'description_main', 0);
const descriptionData = traverseData(payload, (item) => item.className === 'description_top', 0);
const description = traverseData(descriptionData, (item) => item.className === 'info', 0)?.children;
profile.entryId = actorData?.id;
profile.birthPlace = getBioItem(bioData, 'born');
profile.dateOfBirth = getBioItem(bioData, 'birthdate');
profile.age = getBioItem(bioData, 'age');
profile.eyes = getBioItem(bioData, 'eyes');
profile.hairColor = getBioItem(bioData, 'hair');
profile.weight = convert(getBioItem(bioData, 'weight'), 'lb', 'kg');
profile.height = convert(getBioItem(bioData, 'height'), 'cm');
profile.measurements = getBioItem(bioData, 'measurements');
if (!/no bio available/i.test(description)) {
profile.description = description;
}
if (actorData.is_thumbed && actorData.id) {
profile.avatar = [
`${channel.origin}/images/models/${actorData.id}.jpg`, // 628px
`${channel.origin}/images/models/${actorData.id}-516.jpg`,
`${channel.origin}/images/models/${actorData.id}-408.jpg`,
`${channel.origin}/images/models/${actorData.id}-300.jpg`,
];
}
return profile;
}
async function fetchProfile({ name: actorName, url: actorUrl }, entity) {
const { res, url } = await tryUrls([
actorUrl && `${actorUrl}.txt`,
`${entity.url}/tour/model/${slugify(actorName, '-')}.txt`,
]);
if (res.ok) {
const payload = await parsePayload(res.body);
return scrapeProfile(payload, actorName, entity, url.replace(/\.txt$/, ''));
}
return res.status;
}
module.exports = {
fetchLatest,
fetchProfile,
fetchScene,
};

View File

@@ -5,7 +5,6 @@ const unprint = require('unprint');
// const { parse } = require('csv-parse/sync'); // const { parse } = require('csv-parse/sync');
const slugify = require('../utils/slugify'); const slugify = require('../utils/slugify');
const http = require('../utils/http');
const { feetInchesToCm, femaleFeetUsToEu } = require('../utils/convert'); const { feetInchesToCm, femaleFeetUsToEu } = require('../utils/convert');
const thumbKeyRegex = /(thumb\d+_url)|(episode_thumb_image_\d+_url)/; const thumbKeyRegex = /(thumb\d+_url)|(episode_thumb_image_\d+_url)/;
@@ -84,14 +83,14 @@ function scrapeSceneApi(data, channel, parameters) {
async function fetchLatestApi(channel, page = 1, { parameters }) { async function fetchLatestApi(channel, page = 1, { parameters }) {
// JSON API doesn't return poster images, CSV API doesn't have pagination. UPDATE: requested and received both, yet to test // JSON API doesn't return poster images, CSV API doesn't have pagination. UPDATE: requested and received both, yet to test
const res = await http.get(`${parameters.apiAddress}/affiliates?site_id=${parameters.siteId}&page=${page}`, { const res = await unprint.get(`${parameters.apiAddress}/affiliates?site_id=${parameters.siteId}&page=${page}`, {
headers: { headers: {
Authorization: `Bearer ${config.apiKeys[parameters.apiKey]}`, Authorization: `Bearer ${config.apiKeys[parameters.apiKey]}`,
}, },
}); });
if (res.ok) { if (res.ok) {
return res.body.data.map((data) => scrapeSceneApi(data, channel, parameters)); return res.data.data.map((data) => scrapeSceneApi(data, channel, parameters));
} }
return res.status; return res.status;

241
src/scrapers/manyvids.js Executable file
View File

@@ -0,0 +1,241 @@
'use strict';
const unprint = require('unprint');
const slugify = require('../utils/slugify');
function splitTag(tag) {
return tag
.replace(/([a-z])([A-Z])/g, '$1 $2')
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2') // handle sequence of caps
.toLowerCase()
.trim();
}
async function getTrailer(videoId) {
if (!videoId) {
return null;
}
const res = await unprint.get(`https://api.manyvids.com/store/video/${videoId}/private`);
if (res.ok && res.data?.statusCode === 200) {
return res.data.data.teaser.filepath;
}
return null;
}
/*
function getModelUrl(modelData) {
const slug = modelData.slug || modelData.profileUrl?.match(/\/profile\/\d+\/(\w+)/i)?.[1] || null;
if (slug) {
return `https://${slug}.manyvids.com`;
}
return null;
}
*/
async function scrapeScene(data, context) {
const release = {};
release.url = data.url
? `https://www.manyvids.com${data.url}`
: `https://www.manyvids.com/Video/${data.id}/${data.slug || ''}`;
release.entryId = data.id;
release.attributes = {
price: data.price?.regular,
discount: data.price?.discountedPrice,
isCustomOrder: data.isCustomOrder,
isPremium: data.isPremium,
isPrivate: data.isPrivate,
isExclusive: data.isExclusive,
isStreamOnly: data.isStreamOnly,
};
release.title = data.title;
release.description = data.description;
release.date = new Date(data.launchDate);
release.duration = unprint.extractDuration(data.duration || data.videoDuration);
/* many channels operate more like a studio than a performer, so the channel information doesn't represent a person
const model = data.creator || data.model;
release.actors = [{
name: model.stageName || model.displayName,
entryId: model.id,
avatar: model.avatar?.url || model.avatar,
url: getModelUrl(model),
}];
*/
release.tags = [
data.is3D && '3d',
data.isAiGenerated && 'ai',
...data.tagList?.map((tag) => splitTag(tag.label)) || [],
].filter(Boolean);
release.poster = data.thumbnail?.url || data.screenshot || data.thumbnail; // thumbnail URL is full size, thumbnail direct is thumb sized
if (data.preview?.url) {
release.trailer = data.preview?.url;
} else if (context.includeTrailers) {
release.trailer = await getTrailer(data.id);
}
release.qualities = [data.height].filter(Boolean);
return release;
}
const profileIdRegex = /\/profile\/(\d+)/i;
async function getProfileId(channel) {
if (channel.url) {
const { hostname, pathname } = new URL(channel.url);
if (profileIdRegex.test(pathname)) { // full profile URL
const urlProfileId = new URL(channel.url).pathname.match(profileIdRegex)?.[1];
if (urlProfileId) {
return urlProfileId;
}
}
if (/[\w-]+.manyvids.com/.test(hostname)) { // short profile URL
const idRes = await unprint.request(channel.url, {
method: 'head',
followRedirects: 0,
});
const profileId = idRes.headers.get('location')?.match(profileIdRegex)?.[1];
if (profileId) {
return profileId;
}
}
// sadly we can't reliably reconstruct short profile URL from the slug
}
return null;
}
async function fetchLatest(channel, page = 1) {
const profileId = await getProfileId(channel);
if (profileId) {
const res = await unprint.get(`https://api.manyvids.com/store/videos/${profileId}?sort=newest&limit=100&page=${page}`);
if (res.ok && res.data?.statusCode === 200) {
return Promise.all(res.data.data.map(async (data) => scrapeScene(data, channel)));
}
}
return null;
}
async function fetchScene(url, channel, baseRelease, context) {
const videoId = new URL(url).pathname.match(/\/video\/(\d+)/i)?.[1];
if (!videoId) {
return null;
}
const res = await unprint.get(`https://api.manyvids.com/store/video/${videoId}`);
if (res.ok && res.data?.statusCode === 200) {
return scrapeScene(res.data.data, {
channel,
includeTrailers: context.includeTrailers,
isDeep: !!baseRelease.entryId,
});
}
return res.status;
}
function scrapeProfile(data) {
const profile = {};
profile.description = [data.bio, data.description].filter(Boolean).join(' | ') || null;
profile.placeOfResidence = data.location || null;
profile.ethnicity = data.ethnicity || null;
profile.hairColor = data.hairColor || null;
profile.gender = data.identification?.toLowerCase() || null;
profile.orientation = data.orientation;
profile.avatar = data.avatar || null;
profile.photos = [data.portrait].filter(Boolean);
profile.socials = [data.socLinkTwitter, data.socLinkInstagram, data.socLinkReddit].filter(Boolean);
const dateOfBirth = data.dob && unprint.extractDate(data.dob);
if (dateOfBirth && new Date().getFullYear() - dateOfBirth.getFullYear() > 18) {
// date of births that would not be legal are likely bogus
profile.dateOfBirth = dateOfBirth;
}
return profile;
}
async function getActorUrl(actor) {
if (actor.url) {
const idRes = await unprint.request(actor.url, {
method: 'head',
followRedirects: 0,
});
const profileId = idRes.headers.get('location')?.match(profileIdRegex)?.[1];
if (profileId) {
return `https://api.manyvids.com/profile/profile/${profileId}`;
}
}
const res = await unprint.get(`https://api.manyvids.com/search/autocomplete?keywords=${slugify(actor.name, '+')}`);
if (res.ok) {
const model = res.data?.models?.find((modelResult) => modelResult.stage_name === actor.name || modelResult.username === actor.name);
if (model) {
const profileId = model.url?.match(/\/profile\/(\d+)/i)?.[1];
if (profileId) {
return `https://api.manyvids.com/profile/profile/${profileId}`;
}
}
}
return null;
}
async function fetchProfile(actor, entity) {
const actorUrl = await getActorUrl(actor);
if (actorUrl) {
const res = await unprint.get(actorUrl);
if (res.ok && res.data?.username) {
return scrapeProfile(res.data, entity);
}
return res.status;
}
return actorUrl;
}
module.exports = {
fetchLatest,
fetchProfile,
fetchScene,
};

View File

@@ -26,9 +26,11 @@ async function getPhotos(albumUrl) {
selectAll: '.photo-thumb', selectAll: '.photo-thumb',
}); });
return res.ok if (res.ok) {
? res.context.map(({ query }) => unprint.prefixUrl(query.element('source').srcset)) return res.context.map(({ query }) => query.sourceSet('source, picture img')).filter(Boolean);
: []; }
return [];
} }
function scrapeAll(scenes, entity) { function scrapeAll(scenes, entity) {
@@ -37,7 +39,7 @@ function scrapeAll(scenes, entity) {
release.title = query.content('.title a'); release.title = query.content('.title a');
release.url = stripQuery(unprint.prefixUrl(query.url('.title a'), entity.url)); release.url = stripQuery(unprint.prefixUrl(query.url('.title a'), entity.url));
release.entryId = Number(new URL(release.url).pathname.match(/\/watch\/(\d+)/)[1]); release.entryId = Number(new URL(release.url).pathname.match(/\/(?:watch|stream)\/(\d+)/)[1]);
release.date = query.date('.date', 'MMM D, YYYY'); release.date = query.date('.date', 'MMM D, YYYY');
@@ -51,7 +53,8 @@ function scrapeAll(scenes, entity) {
release.actors = query.content('.models', { trim: false })?.trim().split(/\s{2,}/); release.actors = query.content('.models', { trim: false })?.trim().split(/\s{2,}/);
} }
const poster = query.sourceSet('img', 'data-srcset')?.[0]; const poster = query.sourceSet('img', 'data-srcset')?.[0]
|| query.sourceSet('img', 'data-src')?.[0];
release.poster = poster && [ release.poster = poster && [
poster.replace('_640', '_1280'), poster.replace('_640', '_1280'),

View File

@@ -32,6 +32,7 @@ const hush = require('./hush');
const innofsin = require('./innofsin'); const innofsin = require('./innofsin');
const insex = require('./insex'); const insex = require('./insex');
const inthecrack = require('./inthecrack'); const inthecrack = require('./inthecrack');
const jaxslayher = require('./jaxslayher');
const jesseloadsmonsterfacials = require('./jesseloadsmonsterfacials'); const jesseloadsmonsterfacials = require('./jesseloadsmonsterfacials');
const julesjordan = require('./julesjordan'); const julesjordan = require('./julesjordan');
const karups = require('./karups'); const karups = require('./karups');
@@ -43,6 +44,7 @@ const pornbox = require('./pornbox');
const littlecapricedreams = require('./littlecapricedreams'); const littlecapricedreams = require('./littlecapricedreams');
const loveherfilms = require('./loveherfilms'); const loveherfilms = require('./loveherfilms');
const bluedonkeymedia = require('./bluedonkeymedia'); const bluedonkeymedia = require('./bluedonkeymedia');
const manyvids = require('./manyvids');
const mikeadriano = require('./mikeadriano'); const mikeadriano = require('./mikeadriano');
const aylo = require('./aylo'); const aylo = require('./aylo');
const missax = require('./missax'); const missax = require('./missax');
@@ -145,9 +147,11 @@ module.exports = {
julesjordan, julesjordan,
karups, karups,
// etc. // etc.
jaxslayher,
littlecapricedreams, littlecapricedreams,
loveherfilms, loveherfilms,
mamacitaz: porndoe, mamacitaz: porndoe,
manyvids,
mariskax, mariskax,
mikeadriano, mikeadriano,
missax, missax,

View File

@@ -10,9 +10,12 @@ const dateRegex = /\d{4}-\d{2}-\d{2}T/;
function scrapeLatest(scenes, fullData, channel, parameters) { function scrapeLatest(scenes, fullData, channel, parameters) {
return scenes.map(({ query }) => { return scenes.map(({ query }) => {
const release = {}; const release = {};
const origin = new URL(parameters.latest || channel.url).origin;
release.url = query.url('[href*="/video"]', { origin: new URL(parameters.latest || channel.url).origin }); release.url = query.url('a[href*="/video"]', { origin })
release.title = query.content('a[href*="/video"] strong'); || query.url('nuxtlink[to*="/video"]', { attribute: 'to', origin });
release.title = query.content('a[href*="/video"] strong, nuxtlink[to*="/video"] strong');
release.entryId = release.url release.entryId = release.url
? new URL(release.url).pathname.split('/').at(-1) ? new URL(release.url).pathname.split('/').at(-1)

31
src/tools/biometrics.js Normal file
View File

@@ -0,0 +1,31 @@
'use strict';
const yargs = require('yargs');
const { hideBin } = require('yargs/helpers');
const knex = require('../knex');
const { setBiometrics } = require('../biometrics');
const argv = yargs(hideBin(process.argv))
.option('actor-ids', {
alias: 'actors',
type: 'array',
describe: 'List of actor IDs to derive biometrics for',
})
.option('update', {
alias: 'force',
type: 'boolean',
describe: 'Re-compute biometrics when already present',
})
.option('photos', {
type: 'boolean',
describe: 'Include secondary actor photos',
})
.argv;
async function init() {
await setBiometrics(argv.actorIds, argv.update, argv.photos);
knex.destroy();
}
init();

View File

@@ -265,6 +265,7 @@ const actors = [
{ entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] }, { entity: 'tokyohot', name: 'Mai Kawana', url: 'https://my.tokyo-hot.com/cast/2099/', fields: ['avatar', 'birthPlace', 'height', 'cup', 'bust', 'waist', 'hip', 'hairStyle', 'shoeSize', 'bloodType'] },
{ entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] }, { entity: 'wakeupnfuck', name: 'Abby Lee Brazil', fields: ['avatar', 'nationality'] },
{ entity: 'darkkotv', name: 'Aidra Fox', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'measurements', 'naturalBoobs', 'hasTattoos', 'hasPiercings'] }, { entity: 'darkkotv', name: 'Aidra Fox', fields: ['avatar', 'description', 'dateOfBirth', 'birthPlace', 'ethnicity', 'height', 'weight', 'measurements', 'naturalBoobs', 'hasTattoos', 'hasPiercings'] },
{ entity: 'jaxslayher', name: 'Eliza Ibarra', fields: ['avatar', 'description', 'eyes', 'height', 'weight', 'birthPlace', 'measurements', 'hairColor'] },
]; ];
const actorScrapers = scrapers.actors; const actorScrapers = scrapers.actors;