diff --git a/assets/components/alerts/add.vue b/assets/components/alerts/add.vue index 4983ee389..922f1c239 100755 --- a/assets/components/alerts/add.vue +++ b/assets/components/alerts/add.vue @@ -3,6 +3,11 @@ title="Add alert" @close="$emit('close')" > +
{{ error }}
+
+ +
+

Matching

+ + +
@@ -194,7 +265,7 @@
@@ -210,16 +281,23 @@ import Checkbox from '../form/checkbox.vue'; import Search from './search.vue'; async function addAlert() { - await this.$store.dispatch('addAlert', { - actors: this.actors.map((actor) => actor.id), - tags: this.tags.map((tag) => tag.id), - entity: this.entity?.id, - notify: this.notify, - email: this.email, - stashes: this.stashes.map((stash) => stash.id), - }); + this.error = null; - this.$emit('close', true); + try { + await this.$store.dispatch('addAlert', { + actors: this.actors.map((actor) => actor.id), + tags: this.tags.map((tag) => tag.id), + matches: this.matches, + entity: this.entity?.id, + notify: this.notify, + email: this.email, + stashes: this.stashes.map((stash) => stash.id), + }); + + this.$emit('close', true); + } catch (error) { + this.error = error.message; + } } function addActor(actor) { @@ -255,6 +333,26 @@ function removeTag(tag) { this.tags = this.tags.filter((listedTag) => listedTag.id !== tag.id); } +function addMatch() { + if (!this.matchExpression) { + return; + } + + this.matches = this.matches.concat({ + property: this.matchProperty, + expression: this.matchExpression, + }); + + this.matchProperty = 'title'; + this.matchExpression = null; + + this.events.emit('blur'); +} + +function removeMatch(removeIndex) { + this.matches = this.matches.filter((match, matchIndex) => matchIndex !== removeIndex); +} + function addStash(stash) { if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) { this.stashes = this.stashes.concat(stash); @@ -277,9 +375,13 @@ export default { emits: ['close'], data() { return { + error: null, actors: [], tags: [], entity: null, + matches: [], + matchProperty: 'title', + matchExpression: null, notify: true, email: false, stashes: [], @@ -290,10 +392,12 @@ export default { addActor, addAlert, addEntity, + addMatch, addTag, addStash, removeActor, removeEntity, + removeMatch, removeTag, removeStash, }, @@ -325,6 +429,15 @@ export default { font-weight: normal; } +.dialog-error { + padding: 1rem; + margin-bottom: 1rem; + background: var(--error); + color: var(--text-light); + font-weight: bold; + text-align: center; +} + .alert-heading { margin: .75rem 0 .25rem 0; } @@ -338,6 +451,34 @@ export default { font-size: 0; } +.match { + display: flex; + align-items: center; + padding: .25rem 0; + font-family: inherit; + + .remove { + position: relative; + top: -.1rem; + right: 0; + } +} + +.match-property { + text-transform: capitalize; + color: var(--shadow); +} + +.match-expression { + flex-grow: 1; +} + +.match-slash { + padding: 0 .1rem; + color: var(--primary); + font-weight: bold; +} + .actors > .actor, .entity, .tag, @@ -366,6 +507,14 @@ export default { color: var(--text); } +.pattern-tooltip { + display: flex; + gap: .5rem; + position: relative; + padding: .5rem; + overflow: hidden; +} + .remove { width: 1rem; height: 1rem; diff --git a/assets/components/header/notifications.vue b/assets/components/header/notifications.vue index 5aef21eae..696a66dde 100755 --- a/assets/components/header/notifications.vue +++ b/assets/components/header/notifications.vue @@ -99,7 +99,7 @@ @@ -145,12 +145,12 @@ export default { default: 0, }, }, + emits: ['addAlert'], data() { return { showAddAlert: false, }; }, - emits: ['addAlert'], methods: { checkNotifications, checkNotification, diff --git a/assets/components/tooltip/tooltip.vue b/assets/components/tooltip/tooltip.vue index 5a897fd48..9e9ba9ad2 100755 --- a/assets/components/tooltip/tooltip.vue +++ b/assets/components/tooltip/tooltip.vue @@ -125,6 +125,7 @@ function mounted() { } export default { + emits: ['open', 'close'], data() { return { opened: false, @@ -133,7 +134,6 @@ export default { arrowOffset: 0, }; }, - emits: ['open', 'close'], mounted, methods: { calculate, diff --git a/assets/components/users/alert.vue b/assets/components/users/alert.vue index 98940148b..a1c4a735a 100755 --- a/assets/components/users/alert.vue +++ b/assets/components/users/alert.vue @@ -93,17 +93,43 @@
-

Channel

+

{{ alert.entities.length > 1 ? 'Channels' : 'Channel' }}

+ +
+

Matches

+ +
    +
  • + /{{ match.expression.slice(1, -1) }}/ + + {{ match.expression }} +
  • +
+
@@ -227,26 +253,34 @@ export default { } .alert-actors, -.alert-tags { +.alert-tags, +.alert-matches { display: flex; grid-gap: .5rem; } -.tag { +.tag, +.match { color: var(--shadow-strong); padding: .5rem; border: solid 1px var(--shadow-hint); font-size: .9rem; font-weight: bold; +} - &:hover { - cursor: pointer; - border: solid 1px var(--primary); - } +.tag:hover { + cursor: pointer; + border: solid 1px var(--primary); } .entity { width: 10rem; height: 2.5rem; } + +.match-slash { + padding: 0 .1rem; + color: var(--primary); + font-weight: bold; +} diff --git a/assets/js/curate.js b/assets/js/curate.js index ac135cb39..8fe69880c 100755 --- a/assets/js/curate.js +++ b/assets/js/curate.js @@ -191,6 +191,10 @@ function curateAlert(alert) { curatedAlert.entity = curateEntity(alert.entity.entity || alert.entity); } + if (alert.entities) { + curatedAlert.entities = alert.entities.map((entity) => curateEntity(entity.entity || entity)); + } + if (alert.stashes) { curatedAlert.stashes = alert.stashes.map((stash) => curateStash(stash.stash || stash)); } diff --git a/assets/js/ui/actions.js b/assets/js/ui/actions.js index c78e61c5a..6ce707b0a 100755 --- a/assets/js/ui/actions.js +++ b/assets/js/ui/actions.js @@ -82,7 +82,7 @@ function initUiActions(store, _router) { slug } } - entity: alertsEntity { + entities: alertsEntities { entity { id name @@ -90,6 +90,10 @@ function initUiActions(store, _router) { independent } } + matches: alertsMatches { + property + expression + } } } totalCount diff --git a/assets/js/users/actions.js b/assets/js/users/actions.js index c0937e29d..7ff529db6 100755 --- a/assets/js/users/actions.js +++ b/assets/js/users/actions.js @@ -85,12 +85,17 @@ function initUsersActions(store, _router) { slug } } + matches: alertsMatches { + id + property + expression + } actors: alertsActors { actor { ${actorFields} } } - entity: alertsEntity { + entities: alertsEntities { entity { id name diff --git a/migrations/20231109011513_alert_andor_pattern.js b/migrations/20231109011513_alert_andor_pattern.js new file mode 100644 index 000000000..3bab32ef5 --- /dev/null +++ b/migrations/20231109011513_alert_andor_pattern.js @@ -0,0 +1,38 @@ +const config = require('config'); + +exports.up = async (knex) => { + await knex.schema.alterTable('alerts', (table) => { + table.boolean('all') + .defaultTo(true); + }); + + await knex.schema.alterTable('alerts_entities', (table) => { + table.dropUnique('alert_id'); + }); + + await knex.schema.createTable('alerts_matches', (table) => { + table.increments('id'); + + table.integer('alert_id') + .references('id') + .inTable('alerts') + .onDelete('cascade'); + + table.string('property'); + table.string('expression'); + }); + + await knex.raw(` + GRANT SELECT ON alerts_matches TO :visitor; + `, { + visitor: knex.raw(config.database.query.user), + }); +}; + +exports.down = async (knex) => { + await knex.schema.alterTable('alerts', (table) => { + table.dropColumn('all'); + }); + + await knex.schema.dropTable('alerts_matches'); +}; diff --git a/package-lock.json b/package-lock.json index 5dce469e1..64f5ab4e6 100755 --- a/package-lock.json +++ b/package-lock.json @@ -36,6 +36,7 @@ "dayjs": "^1.8.21", "dompurify": "^2.0.11", "ejs": "^3.0.1", + "escape-string-regexp": "^4.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "express-react-views": "^0.11.0", @@ -5205,6 +5206,14 @@ "node": ">=4" } }, + "node_modules/chalk/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/chardet": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", @@ -7077,11 +7086,14 @@ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "engines": { - "node": ">=0.8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -7691,17 +7703,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint/node_modules/eslint-scope": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", @@ -8451,6 +8452,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/figures/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -11599,17 +11608,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/matcher/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -12579,6 +12577,15 @@ "node": ">=0.10.0" } }, + "node_modules/node-sass/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/node-sass/node_modules/gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -17734,17 +17741,6 @@ "node": ">=8" } }, - "node_modules/unprint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unprint/node_modules/eslint": { "version": "8.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", @@ -23152,6 +23148,13 @@ "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + } } }, "chardet": { @@ -24605,9 +24608,9 @@ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "escodegen": { "version": "2.0.0", @@ -24739,11 +24742,6 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, "eslint-scope": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz", @@ -25627,6 +25625,13 @@ "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "requires": { "escape-string-regexp": "^1.0.5" + }, + "dependencies": { + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + } } }, "file-entry-cache": { @@ -28002,13 +28007,6 @@ "integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==", "requires": { "escape-string-regexp": "^4.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - } } }, "media-typer": { @@ -28763,6 +28761,12 @@ "supports-color": "^2.0.0" } }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -32619,11 +32623,6 @@ } } }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" - }, "eslint": { "version": "8.26.0", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", diff --git a/package.json b/package.json index 7634022c6..90f980271 100755 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "dayjs": "^1.8.21", "dompurify": "^2.0.11", "ejs": "^3.0.1", + "escape-string-regexp": "^4.0.0", "express": "^4.17.1", "express-promise-router": "^4.1.0", "express-react-views": "^0.11.0", diff --git a/public/img/logos/julesjordan/lazy/favicon.png b/public/img/logos/julesjordan/lazy/favicon.png old mode 100755 new mode 100644 index 7a221d74c..17710945f Binary files a/public/img/logos/julesjordan/lazy/favicon.png and b/public/img/logos/julesjordan/lazy/favicon.png differ diff --git a/public/img/logos/julesjordan/lazy/favicon_dark.png b/public/img/logos/julesjordan/lazy/favicon_dark.png new file mode 100644 index 000000000..17710945f Binary files /dev/null and b/public/img/logos/julesjordan/lazy/favicon_dark.png differ diff --git a/public/img/logos/julesjordan/lazy/favicon_light.png b/public/img/logos/julesjordan/lazy/favicon_light.png new file mode 100644 index 000000000..17710945f Binary files /dev/null and b/public/img/logos/julesjordan/lazy/favicon_light.png differ diff --git a/public/img/logos/julesjordan/lazy/girlgirl.png b/public/img/logos/julesjordan/lazy/girlgirl.png old mode 100755 new mode 100644 index 7726e313e..f5661774f Binary files a/public/img/logos/julesjordan/lazy/girlgirl.png and b/public/img/logos/julesjordan/lazy/girlgirl.png differ diff --git a/public/img/logos/julesjordan/lazy/julesjordan.png b/public/img/logos/julesjordan/lazy/julesjordan.png old mode 100755 new mode 100644 index 7172ffaf8..242d2fbe3 Binary files a/public/img/logos/julesjordan/lazy/julesjordan.png and b/public/img/logos/julesjordan/lazy/julesjordan.png differ diff --git a/public/img/logos/julesjordan/lazy/manuelferrara.png b/public/img/logos/julesjordan/lazy/manuelferrara.png old mode 100755 new mode 100644 index df15e4729..f33defff1 Binary files a/public/img/logos/julesjordan/lazy/manuelferrara.png and b/public/img/logos/julesjordan/lazy/manuelferrara.png differ diff --git a/public/img/logos/julesjordan/lazy/network.png b/public/img/logos/julesjordan/lazy/network.png old mode 100755 new mode 100644 index a7654991a..82cef5528 Binary files a/public/img/logos/julesjordan/lazy/network.png and b/public/img/logos/julesjordan/lazy/network.png differ diff --git a/public/img/logos/julesjordan/lazy/spermswallowers.png b/public/img/logos/julesjordan/lazy/spermswallowers.png old mode 100755 new mode 100644 index 9285d71f8..972ac4d58 Binary files a/public/img/logos/julesjordan/lazy/spermswallowers.png and b/public/img/logos/julesjordan/lazy/spermswallowers.png differ diff --git a/public/img/logos/julesjordan/lazy/theassfactory.png b/public/img/logos/julesjordan/lazy/theassfactory.png old mode 100755 new mode 100644 index 319d043d3..a56ad33fd Binary files a/public/img/logos/julesjordan/lazy/theassfactory.png and b/public/img/logos/julesjordan/lazy/theassfactory.png differ diff --git a/public/img/logos/julesjordan/misc/the-ass-factory_notld.png b/public/img/logos/julesjordan/misc/the-ass-factory_notld.png new file mode 100644 index 000000000..99341d014 Binary files /dev/null and b/public/img/logos/julesjordan/misc/the-ass-factory_notld.png differ diff --git a/public/img/logos/julesjordan/misc/the-ass-factory_old.png b/public/img/logos/julesjordan/misc/the-ass-factory_old.png new file mode 100755 index 000000000..4897848c0 Binary files /dev/null and b/public/img/logos/julesjordan/misc/the-ass-factory_old.png differ diff --git a/public/img/logos/julesjordan/theassfactory_full.png b/public/img/logos/julesjordan/misc/the-ass-factory_old_full.png similarity index 100% rename from public/img/logos/julesjordan/theassfactory_full.png rename to public/img/logos/julesjordan/misc/the-ass-factory_old_full.png diff --git a/public/img/logos/julesjordan/misc/the-ass-factory_tld.png b/public/img/logos/julesjordan/misc/the-ass-factory_tld.png new file mode 100644 index 000000000..e14fdbb13 Binary files /dev/null and b/public/img/logos/julesjordan/misc/the-ass-factory_tld.png differ diff --git a/public/img/logos/julesjordan/theassfactory.png b/public/img/logos/julesjordan/theassfactory.png old mode 100755 new mode 100644 index 4897848c0..3f3f79cbe Binary files a/public/img/logos/julesjordan/theassfactory.png and b/public/img/logos/julesjordan/theassfactory.png differ diff --git a/public/img/logos/julesjordan/thumbs/favicon.png b/public/img/logos/julesjordan/thumbs/favicon.png old mode 100755 new mode 100644 index 1b6df6632..17710945f Binary files a/public/img/logos/julesjordan/thumbs/favicon.png and b/public/img/logos/julesjordan/thumbs/favicon.png differ diff --git a/public/img/logos/julesjordan/thumbs/favicon_dark.png b/public/img/logos/julesjordan/thumbs/favicon_dark.png new file mode 100644 index 000000000..17710945f Binary files /dev/null and b/public/img/logos/julesjordan/thumbs/favicon_dark.png differ diff --git a/public/img/logos/julesjordan/thumbs/favicon_light.png b/public/img/logos/julesjordan/thumbs/favicon_light.png new file mode 100644 index 000000000..17710945f Binary files /dev/null and b/public/img/logos/julesjordan/thumbs/favicon_light.png differ diff --git a/public/img/logos/julesjordan/thumbs/girlgirl.png b/public/img/logos/julesjordan/thumbs/girlgirl.png old mode 100755 new mode 100644 index 6077b595f..cd2c8e167 Binary files a/public/img/logos/julesjordan/thumbs/girlgirl.png and b/public/img/logos/julesjordan/thumbs/girlgirl.png differ diff --git a/public/img/logos/julesjordan/thumbs/julesjordan.png b/public/img/logos/julesjordan/thumbs/julesjordan.png old mode 100755 new mode 100644 index 769684e83..ae479e360 Binary files a/public/img/logos/julesjordan/thumbs/julesjordan.png and b/public/img/logos/julesjordan/thumbs/julesjordan.png differ diff --git a/public/img/logos/julesjordan/thumbs/manuelferrara.png b/public/img/logos/julesjordan/thumbs/manuelferrara.png old mode 100755 new mode 100644 index 01678a1cc..c31782dea Binary files a/public/img/logos/julesjordan/thumbs/manuelferrara.png and b/public/img/logos/julesjordan/thumbs/manuelferrara.png differ diff --git a/public/img/logos/julesjordan/thumbs/network.png b/public/img/logos/julesjordan/thumbs/network.png old mode 100755 new mode 100644 index df81cc418..79342fefa Binary files a/public/img/logos/julesjordan/thumbs/network.png and b/public/img/logos/julesjordan/thumbs/network.png differ diff --git a/public/img/logos/julesjordan/thumbs/spermswallowers.png b/public/img/logos/julesjordan/thumbs/spermswallowers.png old mode 100755 new mode 100644 index 7b0373d94..6bc4cf034 Binary files a/public/img/logos/julesjordan/thumbs/spermswallowers.png and b/public/img/logos/julesjordan/thumbs/spermswallowers.png differ diff --git a/public/img/logos/julesjordan/thumbs/theassfactory.png b/public/img/logos/julesjordan/thumbs/theassfactory.png old mode 100755 new mode 100644 index 78962ea32..4a9a05662 Binary files a/public/img/logos/julesjordan/thumbs/theassfactory.png and b/public/img/logos/julesjordan/thumbs/theassfactory.png differ diff --git a/public/img/tags/blowjob/lazy/0.jpeg b/public/img/tags/blowjob/lazy/0.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/1.jpeg b/public/img/tags/blowjob/lazy/1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/2.jpeg b/public/img/tags/blowjob/lazy/2.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/3.jpeg b/public/img/tags/blowjob/lazy/3.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/4.jpeg b/public/img/tags/blowjob/lazy/4.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/5.jpeg b/public/img/tags/blowjob/lazy/5.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/amy_anderssen_julesjordan.jpeg b/public/img/tags/blowjob/lazy/amy_anderssen_julesjordan.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/anissa_kate_vixen.jpeg b/public/img/tags/blowjob/lazy/anissa_kate_vixen.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/anissa_kate_vixen_1.jpeg b/public/img/tags/blowjob/lazy/anissa_kate_vixen_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/azul_hermosa_brazzers.jpeg b/public/img/tags/blowjob/lazy/azul_hermosa_brazzers.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/azul_hermosa_brazzers_1.jpeg b/public/img/tags/blowjob/lazy/azul_hermosa_brazzers_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/azul_hermosa_realitykings.jpeg b/public/img/tags/blowjob/lazy/azul_hermosa_realitykings.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/cecilia_lion_wefuckblackgirls.jpeg b/public/img/tags/blowjob/lazy/cecilia_lion_wefuckblackgirls.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty.jpeg b/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty_1.jpeg b/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty_2.jpeg b/public/img/tags/blowjob/lazy/clanddi_jinkcego_ddfbusty_2.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/jane_wilde_evilangel.jpeg b/public/img/tags/blowjob/lazy/jane_wilde_evilangel.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/juelz_ventura_babygotboobs.jpeg b/public/img/tags/blowjob/lazy/juelz_ventura_babygotboobs.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/lily_lou_julesjordan.jpeg b/public/img/tags/blowjob/lazy/lily_lou_julesjordan.jpeg new file mode 100644 index 000000000..91f89d120 Binary files /dev/null and b/public/img/tags/blowjob/lazy/lily_lou_julesjordan.jpeg differ diff --git a/public/img/tags/blowjob/lazy/maddy_may_cherrypimps.jpeg b/public/img/tags/blowjob/lazy/maddy_may_cherrypimps.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lazy/pussykat_assholefever.jpeg b/public/img/tags/blowjob/lazy/pussykat_assholefever.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/lily_lou_julesjordan.jpeg b/public/img/tags/blowjob/lily_lou_julesjordan.jpeg new file mode 100644 index 000000000..5b6701843 Binary files /dev/null and b/public/img/tags/blowjob/lily_lou_julesjordan.jpeg differ diff --git a/public/img/tags/blowjob/thumbs/0.jpeg b/public/img/tags/blowjob/thumbs/0.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/1.jpeg b/public/img/tags/blowjob/thumbs/1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/2.jpeg b/public/img/tags/blowjob/thumbs/2.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/3.jpeg b/public/img/tags/blowjob/thumbs/3.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/4.jpeg b/public/img/tags/blowjob/thumbs/4.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/5.jpeg b/public/img/tags/blowjob/thumbs/5.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/amy_anderssen_julesjordan.jpeg b/public/img/tags/blowjob/thumbs/amy_anderssen_julesjordan.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/anissa_kate_vixen.jpeg b/public/img/tags/blowjob/thumbs/anissa_kate_vixen.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/anissa_kate_vixen_1.jpeg b/public/img/tags/blowjob/thumbs/anissa_kate_vixen_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/azul_hermosa_brazzers.jpeg b/public/img/tags/blowjob/thumbs/azul_hermosa_brazzers.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/azul_hermosa_brazzers_1.jpeg b/public/img/tags/blowjob/thumbs/azul_hermosa_brazzers_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/azul_hermosa_realitykings.jpeg b/public/img/tags/blowjob/thumbs/azul_hermosa_realitykings.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/cecilia_lion_wefuckblackgirls.jpeg b/public/img/tags/blowjob/thumbs/cecilia_lion_wefuckblackgirls.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty.jpeg b/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty_1.jpeg b/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty_1.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty_2.jpeg b/public/img/tags/blowjob/thumbs/clanddi_jinkcego_ddfbusty_2.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/jane_wilde_evilangel.jpeg b/public/img/tags/blowjob/thumbs/jane_wilde_evilangel.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/juelz_ventura_babygotboobs.jpeg b/public/img/tags/blowjob/thumbs/juelz_ventura_babygotboobs.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/lily_lou_julesjordan.jpeg b/public/img/tags/blowjob/thumbs/lily_lou_julesjordan.jpeg new file mode 100644 index 000000000..04413a17b Binary files /dev/null and b/public/img/tags/blowjob/thumbs/lily_lou_julesjordan.jpeg differ diff --git a/public/img/tags/blowjob/thumbs/maddy_may_cherrypimps.jpeg b/public/img/tags/blowjob/thumbs/maddy_may_cherrypimps.jpeg old mode 100755 new mode 100644 diff --git a/public/img/tags/blowjob/thumbs/pussykat_assholefever.jpeg b/public/img/tags/blowjob/thumbs/pussykat_assholefever.jpeg old mode 100755 new mode 100644 diff --git a/seeds/04_media.js b/seeds/04_media.js index be082f4ac..d46778aa0 100755 --- a/seeds/04_media.js +++ b/seeds/04_media.js @@ -676,6 +676,7 @@ const tagMedia = [ ['blowbang', 'zaawaadi_roccosiffredi_1', 'Zaawaadi in "My Name Is Zaawaadi"', 'roccosiffredi'], ['blowbang', 1, 'Nicole Black in GIO1680', 'analvids'], ['blowbang', 'gina_gerson_assholefever', 'Gina Gerson in "Oppa Gangbang Style"', 'assholefever'], + ['blowjob', 'lily_lou_julesjordan', 'Lily Lou in "Lily Lou Has Her Ass Explored"', 'julesjordan'], ['blowjob', 'clanddi_jinkcego_ddfbusty_1', 'Clanddi Jinkcego', 'ddfbusty'], ['blowjob', 'juelz_ventura_babygotboobs', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'], ['blowjob', 4, 'Chloe Cherry in "Chloe\'s Big Anal"', 'darkx'], diff --git a/src/alerts.js b/src/alerts.js index 9cbc068e4..db5c52599 100755 --- a/src/alerts.js +++ b/src/alerts.js @@ -1,5 +1,7 @@ 'use strict'; +const escapeRegexp = require('escape-string-regexp'); + const knex = require('./knex'); const bulkInsert = require('./utils/bulk-insert'); const { HttpError } = require('./errors'); @@ -9,10 +11,14 @@ async function addAlert(alert, sessionUser) { throw new HttpError('You are not authenthicated', 401); } - if (!alert.actors?.length > 0 && !alert.tags?.length > 0 && !alert.entity) { + if (!alert.actors?.length > 0 && !alert.tags?.length > 0 && !alert.entity && !alert.matches?.length > 0) { throw new HttpError('Alert must contain at least one actor, tag or entity', 400); } + if (alert.matches?.some((match) => !match.property || !match.expression)) { + throw new HttpError('Match must define a property and an expression', 400); + } + const [alertId] = await knex('alerts') .insert({ user_id: sessionUser.id, @@ -30,6 +36,11 @@ async function addAlert(alert, sessionUser) { alert_id: alertId, tag_id: tagId, })), false), + alert.matches?.length > 0 && bulkInsert('alerts_matches', alert.matches.map((match) => ({ + alert_id: alertId, + property: match.property, + expression: match.expression, + })), false), alert.stashes?.length > 0 && bulkInsert('alerts_stashes', alert.stashes.map((stashId) => ({ alert_id: alertId, stash_id: stashId, @@ -48,88 +59,153 @@ async function removeAlert(alertId) { } async function notify(scenes) { - const releases = await knex.raw(` - SELECT alerts.id as alert_id, alerts.notify, alerts.email, releases.id as scene_id, users.id as user_id, COALESCE(json_agg(alerts_stashes.stash_id) FILTER (WHERE alerts_stashes.stash_id IS NOT NULL), '[]') as stashes - FROM releases - CROSS JOIN alerts - LEFT JOIN users ON users.id = alerts.user_id - LEFT JOIN alerts_stashes ON alerts_stashes.alert_id = alerts.id - /* match updated IDs from input */ - WHERE (releases.id = ANY(:sceneIds)) - /* match tags */ - AND (NOT EXISTS (SELECT alerts_tags.alert_id - FROM alerts_tags - WHERE alerts_tags.alert_id = alerts.id) - OR (SELECT array_agg(releases_tags.tag_id) - FROM releases_tags - WHERE releases_tags.release_id = releases.id - GROUP BY releases_tags.release_id) - @> (SELECT array_agg(alerts_tags.tag_id) - FROM alerts_tags - WHERE alerts_tags.alert_id = alerts.id - GROUP BY alerts_tags.alert_id)) - /* match actors */ - AND (NOT EXISTS (SELECT alerts_actors.alert_id - FROM alerts_actors - WHERE alerts_actors.alert_id = alerts.id) - OR (SELECT array_agg(releases_actors.actor_id) - FROM releases_actors - WHERE releases_actors.release_id = releases.id - GROUP BY releases_actors.release_id) - @> (SELECT array_agg(alerts_actors.actor_id) - FROM alerts_actors - WHERE alerts_actors.alert_id = alerts.id - GROUP BY alerts_actors.alert_id)) - /* match entity */ - AND ((NOT EXISTS (SELECT alerts_entities.entity_id - FROM alerts_entities - WHERE alerts_entities.alert_id = alerts.id)) - OR (releases.entity_id - = ANY(array( - /* include children of entities */ - WITH RECURSIVE included AS ( - SELECT entities.* - FROM alerts_entities - LEFT JOIN entities ON entities.id = alerts_entities.entity_id - WHERE alerts_entities.alert_id = alerts.id + const sceneIds = scenes.map((scene) => scene.id); - UNION ALL + const [ + releasesActors, + releasesTags, + rawAlerts, + alertsActors, + alertsTags, + alertsEntities, + alertsMatches, + alertsStashes, + ] = await Promise.all([ + knex('releases_actors').whereIn('release_id', sceneIds), + knex('releases_tags').whereIn('release_id', sceneIds), + knex('alerts'), + knex('alerts_actors'), + knex('alerts_tags'), + knex('alerts_entities'), + knex('alerts_matches'), + knex('alerts_stashes'), + ]); - SELECT entities.* - FROM entities - INNER JOIN included ON included.id = entities.parent_id - ) + const actorIdsByReleaseId = releasesActors.reduce((acc, releaseActor) => { + if (!acc[releaseActor.release_id]) { + acc[releaseActor.release_id] = []; + } - SELECT included.id - FROM included - GROUP BY included.id - )))) - GROUP BY releases.id, users.id, alerts.id; - `, { - sceneIds: scenes.map((scene) => scene.id), + acc[releaseActor.release_id].push(releaseActor.actor_id); + + return acc; + }, {}); + + const tagIdsByReleaseId = releasesTags.reduce((acc, releaseTag) => { + if (!acc[releaseTag.release_id]) { + acc[releaseTag.release_id] = []; + } + + acc[releaseTag.release_id].push(releaseTag.tag_id); + + return acc; + }, {}); + + const alertsActorsByAlertId = alertsActors.reduce((acc, alertActor) => { if (!acc[alertActor.alert_id]) { acc[alertActor.alert_id] = []; } acc[alertActor.alert_id].push(alertActor.actor_id); return acc; }, {}); + const alertsTagsByAlertId = alertsTags.reduce((acc, alertTag) => { if (!acc[alertTag.alert_id]) { acc[alertTag.alert_id] = []; } acc[alertTag.alert_id].push(alertTag.tag_id); return acc; }, {}); + const alertsEntitiesByAlertId = alertsEntities.reduce((acc, alertEntity) => { if (!acc[alertEntity.alert_id]) { acc[alertEntity.alert_id] = []; } acc[alertEntity.alert_id].push(alertEntity.entity_id); return acc; }, {}); + const alertsStashesByAlertId = alertsStashes.reduce((acc, alertStash) => { if (!acc[alertStash.alert_id]) { acc[alertStash.alert_id] = []; } acc[alertStash.alert_id].push(alertStash.stash_id); return acc; }, {}); + + const alertsMatchesByAlertId = alertsMatches.reduce((acc, alertMatch) => { + if (!acc[alertMatch.alert_id]) { + acc[alertMatch.alert_id] = []; + } + + acc[alertMatch.alert_id].push({ + property: alertMatch.property, + expression: /\/.*\//.test(alertMatch.expression) + ? new RegExp(alertMatch.expression.slice(1, -1), 'ui') + : new RegExp(escapeRegexp(alertMatch.expression), 'ui'), + }); + + return acc; + }, {}); + + console.log(alertsStashesByAlertId); + + const alerts = rawAlerts.map((alert) => ({ + id: alert.id, + userId: alert.user_id, + notify: alert.notify, + email: alert.email, + all: alert.all, + actors: alertsActorsByAlertId[alert.id] || [], + tags: alertsTagsByAlertId[alert.id] || [], + entities: alertsEntitiesByAlertId[alert.id] || [], + matches: alertsMatchesByAlertId[alert.id] || [], + stashes: alertsStashesByAlertId[alert.id] || [], + })); + + const curatedScenes = scenes.map((scene) => ({ + id: scene.id, + title: scene.title, + description: scene.description, + actorIds: actorIdsByReleaseId[scene.id] || [], + tagIds: tagIdsByReleaseId[scene.id] || [], + entityId: scene.entity.id, + parentEntityId: scene.entity.parent?.id, + })); + + const triggers = alerts.flatMap((alert) => { + const alertScenes = curatedScenes.filter((scene) => { + console.log(scene.title, alert.tags, scene.tagIds); + + if (alert.actors.length > 0 && !alert.actors.every((actorId) => scene.actorIds.includes(actorId))) { + console.log('THROW ACTORS'); + return false; + } + + if (alert.tags.length > 0 && !alert.tags.every((tagId) => scene.tagIds.includes(tagId))) { + console.log('THROW TAGS'); + return false; + } + + // multiple entities can only be matched in OR mode + if (alert.entities.length > 0 && !alert.entities.some((alertEntityId) => alertEntityId === scene.entityId || alertEntityId === scene.parentEntityId)) { + console.log('THROW ENTITIES'); + return false; + } + + if (alert.matches.length > 0 && !alert.matches.every((match) => match.expression.test(scene[match.property]))) { + console.log('THROW MATCHES'); + return false; + } + + console.log('OK'); + + return true; + }); + + return alertScenes.map((scene) => ({ + sceneId: scene.id, + alert, + })); }); - const notifications = releases.rows - .filter((alert) => alert.notify) - .map((notification) => ({ - user_id: notification.user_id, - alert_id: notification.alert_id, - scene_id: notification.scene_id, + const notifications = Object.values(Object.fromEntries(triggers // prevent multiple notifications for the same scene + .filter((trigger) => trigger.alert.notify) + .map((trigger) => [`${trigger.alert.userId}:${trigger.sceneId}`, trigger]))) + .map((trigger) => ({ + user_id: trigger.alert.userId, + alert_id: trigger.alert.id, + scene_id: trigger.sceneId, })); - const stashes = releases.rows - .filter((release) => release.stashes.length > 0) - .flatMap((release) => release.stashes.map((stash) => ({ - scene_id: release.scene_id, - stash_id: stash, - }))); + console.log('triggers', triggers); + + const stashes = Object.values(Object.fromEntries(triggers.flatMap((trigger) => trigger.alert.stashes.map((stashId) => ({ + scene_id: trigger.sceneId, + stash_id: stashId, + }))).map((stash) => [`${stash.stash_id}:${stash.scene_id}`, stash]))); + + console.log('stashes', stashes); await Promise.all([ bulkInsert('notifications', notifications, false), bulkInsert('stashes_scenes', stashes, false), ]); - return releases.rows; + return triggers; } async function updateNotification(notificationId, notification, sessionUser) { diff --git a/src/scrapers/julesjordan.js b/src/scrapers/julesjordan.js index 584fb456b..550820d3b 100755 --- a/src/scrapers/julesjordan.js +++ b/src/scrapers/julesjordan.js @@ -32,7 +32,7 @@ function scrapeAll(scenes, site, entryIdFromTitle) { release.title = title?.slice(0, title.match(/starring:/i)?.index || Infinity).trim(); release.url = query.url('.content_img a, .dvd_info > a, a.update_title, a[title]'); - release.date = query.date('.update_date', 'MM/DD/YYYY'); + release.date = query.date('.update_date', ['MM/DD/YYYY', 'YYYY-MM-DD']); release.entryId = (entryIdFromTitle && slugify(release.title)) || element.dataset.setid || query.element('.rating_box')?.dataset.id || query.attribute('a img', 'id')?.match(/set-target-(\d+)/)?.[1]; @@ -81,7 +81,7 @@ function scrapeUpcoming(scenes, channel) { const release = {}; release.title = query.text('.overlay-text', { join: false })?.[0]; - release.date = query.date('.overlay-text', 'MM/DD/YYYY'); + release.date = query.date('.overlay-text', ['MM/DD/YYYY', 'YYYY-MM-DD']); release.actors = query.all('.update_models a').map((actorEl) => ({ name: unprint.query.content(actorEl), @@ -159,7 +159,7 @@ async function scrapeScene({ html, query }, context) { release.description = query.content('.update_description') || query.text('//div[./span[contains(text(), "Description")]]'); release.entryId = context.entity.parameters?.entryIdFromTitle ? slugify(release.title) : getEntryId(html); - release.date = query.date(['.update_date', '//div[./span[contains(text(), "Date")]]'], 'MM/DD/YYYY'); + release.date = query.date(['.update_date', '//div[./span[contains(text(), "Date")]]'], ['MM/DD/YYYY', 'YYYY-MM-DD']); release.actors = query.all('.backgroundcolor_info > .update_models a, .item .update_models a, .player-scene-description .update_models a').map((actorEl) => ({ name: unprint.query.content(actorEl),