Refactored alerts to use application code, added regex. Updated Jules Jordan for the Ass Factory relaunch.
|
@ -3,6 +3,11 @@
|
||||||
title="Add alert"
|
title="Add alert"
|
||||||
@close="$emit('close')"
|
@close="$emit('close')"
|
||||||
>
|
>
|
||||||
|
<div
|
||||||
|
v-if="error"
|
||||||
|
class="dialog-error"
|
||||||
|
>{{ error }}</div>
|
||||||
|
|
||||||
<form
|
<form
|
||||||
class="dialog-body"
|
class="dialog-body"
|
||||||
@submit.prevent="addAlert"
|
@submit.prevent="addAlert"
|
||||||
|
@ -136,6 +141,72 @@
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="alert-section">
|
||||||
|
<h4 class="alert-heading">Matching</h4>
|
||||||
|
|
||||||
|
<ul class="matches nolist">
|
||||||
|
<li
|
||||||
|
v-for="(match, index) in matches"
|
||||||
|
:key="`match-${index}`"
|
||||||
|
class="match"
|
||||||
|
>
|
||||||
|
<span class="match-property">{{ match.property }}: </span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-if="match.expression.slice(0, 1) === '/' && match.expression.slice(-1) === '/'"
|
||||||
|
class="match-expression"
|
||||||
|
><span class="match-slash">/</span>{{ match.expression.slice(1, -1) }}<span class="match-slash">/</span></span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="match-expression"
|
||||||
|
>{{ match.expression }}</span>
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon="cross3"
|
||||||
|
class="remove"
|
||||||
|
@click.native="removeMatch(index)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<Tooltip
|
||||||
|
v-if="!entity"
|
||||||
|
@open="$refs.expression?.focus()"
|
||||||
|
>
|
||||||
|
<li class="match placeholder">
|
||||||
|
Anything
|
||||||
|
|
||||||
|
<Icon
|
||||||
|
icon="plus3"
|
||||||
|
class="add"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<template #tooltip>
|
||||||
|
<form
|
||||||
|
class="pattern-tooltip"
|
||||||
|
@submit.prevent="addMatch"
|
||||||
|
>
|
||||||
|
<select
|
||||||
|
v-model="matchProperty"
|
||||||
|
class="input"
|
||||||
|
>
|
||||||
|
<option value="title">Title</option>
|
||||||
|
<option value="description">Description</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input
|
||||||
|
ref="expression"
|
||||||
|
v-model="matchExpression"
|
||||||
|
class="input"
|
||||||
|
placeholder="Expression, // for RegExp"
|
||||||
|
>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
</Tooltip>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-section">
|
<div class="dialog-section">
|
||||||
|
@ -194,7 +265,7 @@
|
||||||
|
|
||||||
<div class="dialog-actions right">
|
<div class="dialog-actions right">
|
||||||
<button
|
<button
|
||||||
:disabled="actors.length === 0 && tags.length === 0 && !entity"
|
:disabled="actors.length === 0 && tags.length === 0 && !entity && matches.length === 0"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="button button-primary"
|
class="button button-primary"
|
||||||
>Add alert</button>
|
>Add alert</button>
|
||||||
|
@ -210,9 +281,13 @@ import Checkbox from '../form/checkbox.vue';
|
||||||
import Search from './search.vue';
|
import Search from './search.vue';
|
||||||
|
|
||||||
async function addAlert() {
|
async function addAlert() {
|
||||||
|
this.error = null;
|
||||||
|
|
||||||
|
try {
|
||||||
await this.$store.dispatch('addAlert', {
|
await this.$store.dispatch('addAlert', {
|
||||||
actors: this.actors.map((actor) => actor.id),
|
actors: this.actors.map((actor) => actor.id),
|
||||||
tags: this.tags.map((tag) => tag.id),
|
tags: this.tags.map((tag) => tag.id),
|
||||||
|
matches: this.matches,
|
||||||
entity: this.entity?.id,
|
entity: this.entity?.id,
|
||||||
notify: this.notify,
|
notify: this.notify,
|
||||||
email: this.email,
|
email: this.email,
|
||||||
|
@ -220,6 +295,9 @@ async function addAlert() {
|
||||||
});
|
});
|
||||||
|
|
||||||
this.$emit('close', true);
|
this.$emit('close', true);
|
||||||
|
} catch (error) {
|
||||||
|
this.error = error.message;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addActor(actor) {
|
function addActor(actor) {
|
||||||
|
@ -255,6 +333,26 @@ function removeTag(tag) {
|
||||||
this.tags = this.tags.filter((listedTag) => listedTag.id !== tag.id);
|
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) {
|
function addStash(stash) {
|
||||||
if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) {
|
if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) {
|
||||||
this.stashes = this.stashes.concat(stash);
|
this.stashes = this.stashes.concat(stash);
|
||||||
|
@ -277,9 +375,13 @@ export default {
|
||||||
emits: ['close'],
|
emits: ['close'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
error: null,
|
||||||
actors: [],
|
actors: [],
|
||||||
tags: [],
|
tags: [],
|
||||||
entity: null,
|
entity: null,
|
||||||
|
matches: [],
|
||||||
|
matchProperty: 'title',
|
||||||
|
matchExpression: null,
|
||||||
notify: true,
|
notify: true,
|
||||||
email: false,
|
email: false,
|
||||||
stashes: [],
|
stashes: [],
|
||||||
|
@ -290,10 +392,12 @@ export default {
|
||||||
addActor,
|
addActor,
|
||||||
addAlert,
|
addAlert,
|
||||||
addEntity,
|
addEntity,
|
||||||
|
addMatch,
|
||||||
addTag,
|
addTag,
|
||||||
addStash,
|
addStash,
|
||||||
removeActor,
|
removeActor,
|
||||||
removeEntity,
|
removeEntity,
|
||||||
|
removeMatch,
|
||||||
removeTag,
|
removeTag,
|
||||||
removeStash,
|
removeStash,
|
||||||
},
|
},
|
||||||
|
@ -325,6 +429,15 @@ export default {
|
||||||
font-weight: normal;
|
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 {
|
.alert-heading {
|
||||||
margin: .75rem 0 .25rem 0;
|
margin: .75rem 0 .25rem 0;
|
||||||
}
|
}
|
||||||
|
@ -338,6 +451,34 @@ export default {
|
||||||
font-size: 0;
|
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,
|
.actors > .actor,
|
||||||
.entity,
|
.entity,
|
||||||
.tag,
|
.tag,
|
||||||
|
@ -366,6 +507,14 @@ export default {
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pattern-tooltip {
|
||||||
|
display: flex;
|
||||||
|
gap: .5rem;
|
||||||
|
position: relative;
|
||||||
|
padding: .5rem;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
.remove {
|
.remove {
|
||||||
width: 1rem;
|
width: 1rem;
|
||||||
height: 1rem;
|
height: 1rem;
|
||||||
|
|
|
@ -99,7 +99,7 @@
|
||||||
|
|
||||||
<Icon
|
<Icon
|
||||||
v-if="notification.alert"
|
v-if="notification.alert"
|
||||||
v-tooltip="`You set an alert for <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'all'}</strong> scenes with <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> for <strong>${notification.alert.entity?.name || 'any channel'}</strong>`"
|
v-tooltip="`You set an alert for <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'all'}</strong> scenes with <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> from <strong>${notification.alert.entities.map((entity) => entity.name).join(', ') || 'any channel'}</strong> matching <strong>${notification.alert.matches.map((match) => `${match.property}: ${match.expression}`).join(', ') || 'anything'}</strong>`"
|
||||||
icon="question5"
|
icon="question5"
|
||||||
@click.prevent.stop
|
@click.prevent.stop
|
||||||
/>
|
/>
|
||||||
|
@ -145,12 +145,12 @@ export default {
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
emits: ['addAlert'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showAddAlert: false,
|
showAddAlert: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
emits: ['addAlert'],
|
|
||||||
methods: {
|
methods: {
|
||||||
checkNotifications,
|
checkNotifications,
|
||||||
checkNotification,
|
checkNotification,
|
||||||
|
|
|
@ -125,6 +125,7 @@ function mounted() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
emits: ['open', 'close'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
opened: false,
|
opened: false,
|
||||||
|
@ -133,7 +134,6 @@ export default {
|
||||||
arrowOffset: 0,
|
arrowOffset: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
emits: ['open', 'close'],
|
|
||||||
mounted,
|
mounted,
|
||||||
methods: {
|
methods: {
|
||||||
calculate,
|
calculate,
|
||||||
|
|
|
@ -93,17 +93,43 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
v-if="alert.entity"
|
v-if="alert.entities.length > 0"
|
||||||
class="alert-section alert-trigger"
|
class="alert-section alert-trigger"
|
||||||
>
|
>
|
||||||
<h4 class="alert-heading">Channel</h4>
|
<h4 class="alert-heading">{{ alert.entities.length > 1 ? 'Channels' : 'Channel' }}</h4>
|
||||||
|
|
||||||
<Entity
|
<Entity
|
||||||
v-if="alert.entity"
|
v-for="entity in alert.entities"
|
||||||
:entity="alert.entity"
|
:key="`${alert.id}${entity.id}`"
|
||||||
|
:entity="entity"
|
||||||
class="entity"
|
class="entity"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-if="alert.matches.length > 0"
|
||||||
|
class="alert-section alert-trigger"
|
||||||
|
>
|
||||||
|
<h4 class="alert-heading">Matches</h4>
|
||||||
|
|
||||||
|
<ul class="alert-matches nolist">
|
||||||
|
<li
|
||||||
|
v-for="match in alert.matches"
|
||||||
|
:key="`match-${match.id}`"
|
||||||
|
class="match"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
v-if="match.expression.slice(0, 1) === '/' && match.expression.slice(-1) === '/'"
|
||||||
|
class="match-expression"
|
||||||
|
><span class="match-slash">/</span>{{ match.expression.slice(1, -1) }}<span class="match-slash">/</span></span>
|
||||||
|
|
||||||
|
<span
|
||||||
|
v-else
|
||||||
|
class="match-expression"
|
||||||
|
>{{ match.expression }}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -227,26 +253,34 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-actors,
|
.alert-actors,
|
||||||
.alert-tags {
|
.alert-tags,
|
||||||
|
.alert-matches {
|
||||||
display: flex;
|
display: flex;
|
||||||
grid-gap: .5rem;
|
grid-gap: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tag {
|
.tag,
|
||||||
|
.match {
|
||||||
color: var(--shadow-strong);
|
color: var(--shadow-strong);
|
||||||
padding: .5rem;
|
padding: .5rem;
|
||||||
border: solid 1px var(--shadow-hint);
|
border: solid 1px var(--shadow-hint);
|
||||||
font-size: .9rem;
|
font-size: .9rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
.tag:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: solid 1px var(--primary);
|
border: solid 1px var(--primary);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.entity {
|
.entity {
|
||||||
width: 10rem;
|
width: 10rem;
|
||||||
height: 2.5rem;
|
height: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.match-slash {
|
||||||
|
padding: 0 .1rem;
|
||||||
|
color: var(--primary);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -191,6 +191,10 @@ function curateAlert(alert) {
|
||||||
curatedAlert.entity = curateEntity(alert.entity.entity || alert.entity);
|
curatedAlert.entity = curateEntity(alert.entity.entity || alert.entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (alert.entities) {
|
||||||
|
curatedAlert.entities = alert.entities.map((entity) => curateEntity(entity.entity || entity));
|
||||||
|
}
|
||||||
|
|
||||||
if (alert.stashes) {
|
if (alert.stashes) {
|
||||||
curatedAlert.stashes = alert.stashes.map((stash) => curateStash(stash.stash || stash));
|
curatedAlert.stashes = alert.stashes.map((stash) => curateStash(stash.stash || stash));
|
||||||
}
|
}
|
||||||
|
|
|
@ -82,7 +82,7 @@ function initUiActions(store, _router) {
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entity: alertsEntity {
|
entities: alertsEntities {
|
||||||
entity {
|
entity {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
@ -90,6 +90,10 @@ function initUiActions(store, _router) {
|
||||||
independent
|
independent
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
matches: alertsMatches {
|
||||||
|
property
|
||||||
|
expression
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
totalCount
|
totalCount
|
||||||
|
|
|
@ -85,12 +85,17 @@ function initUsersActions(store, _router) {
|
||||||
slug
|
slug
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
matches: alertsMatches {
|
||||||
|
id
|
||||||
|
property
|
||||||
|
expression
|
||||||
|
}
|
||||||
actors: alertsActors {
|
actors: alertsActors {
|
||||||
actor {
|
actor {
|
||||||
${actorFields}
|
${actorFields}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
entity: alertsEntity {
|
entities: alertsEntities {
|
||||||
entity {
|
entity {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|
|
@ -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');
|
||||||
|
};
|
|
@ -36,6 +36,7 @@
|
||||||
"dayjs": "^1.8.21",
|
"dayjs": "^1.8.21",
|
||||||
"dompurify": "^2.0.11",
|
"dompurify": "^2.0.11",
|
||||||
"ejs": "^3.0.1",
|
"ejs": "^3.0.1",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-promise-router": "^4.1.0",
|
"express-promise-router": "^4.1.0",
|
||||||
"express-react-views": "^0.11.0",
|
"express-react-views": "^0.11.0",
|
||||||
|
@ -5205,6 +5206,14 @@
|
||||||
"node": ">=4"
|
"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": {
|
"node_modules/chardet": {
|
||||||
"version": "0.7.0",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
|
||||||
|
@ -7077,11 +7086,14 @@
|
||||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||||
},
|
},
|
||||||
"node_modules/escape-string-regexp": {
|
"node_modules/escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.8.0"
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/escodegen": {
|
"node_modules/escodegen": {
|
||||||
|
@ -7691,17 +7703,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"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": {
|
"node_modules/eslint/node_modules/eslint-scope": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
|
||||||
|
@ -8451,6 +8452,14 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"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": {
|
"node_modules/file-entry-cache": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
|
"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"
|
"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": {
|
"node_modules/media-typer": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
|
||||||
|
@ -12579,6 +12577,15 @@
|
||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/node-sass/node_modules/gauge": {
|
||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
||||||
|
@ -17734,17 +17741,6 @@
|
||||||
"node": ">=8"
|
"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": {
|
"node_modules/unprint/node_modules/eslint": {
|
||||||
"version": "8.26.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
|
||||||
|
@ -23152,6 +23148,13 @@
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
"supports-color": "^5.3.0"
|
"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": {
|
"chardet": {
|
||||||
|
@ -24605,9 +24608,9 @@
|
||||||
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
|
||||||
},
|
},
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
|
||||||
},
|
},
|
||||||
"escodegen": {
|
"escodegen": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
@ -24739,11 +24742,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
"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": {
|
"eslint-scope": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
|
||||||
|
@ -25627,6 +25625,13 @@
|
||||||
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
"integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"escape-string-regexp": "^1.0.5"
|
"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": {
|
"file-entry-cache": {
|
||||||
|
@ -28002,13 +28007,6 @@
|
||||||
"integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
|
"integrity": "sha512-S6x5wmcDmsDRRU/c2dkccDwQPXoFczc5+HpQ2lON8pnvHlnvHAHj5WlLVvw6n6vNyHuVugYrFohYxbS+pvFpKQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"escape-string-regexp": "^4.0.0"
|
"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": {
|
"media-typer": {
|
||||||
|
@ -28763,6 +28761,12 @@
|
||||||
"supports-color": "^2.0.0"
|
"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": {
|
"gauge": {
|
||||||
"version": "2.7.4",
|
"version": "2.7.4",
|
||||||
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
|
"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": {
|
"eslint": {
|
||||||
"version": "8.26.0",
|
"version": "8.26.0",
|
||||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
|
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz",
|
||||||
|
|
|
@ -95,6 +95,7 @@
|
||||||
"dayjs": "^1.8.21",
|
"dayjs": "^1.8.21",
|
||||||
"dompurify": "^2.0.11",
|
"dompurify": "^2.0.11",
|
||||||
"ejs": "^3.0.1",
|
"ejs": "^3.0.1",
|
||||||
|
"escape-string-regexp": "^4.0.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"express-promise-router": "^4.1.0",
|
"express-promise-router": "^4.1.0",
|
||||||
"express-react-views": "^0.11.0",
|
"express-react-views": "^0.11.0",
|
||||||
|
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 745 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 925 B After Width: | Height: | Size: 961 B |
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 63 KiB |
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
After Width: | Height: | Size: 85 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 5.0 KiB After Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 7.6 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.8 KiB |
After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 6.6 KiB |
After Width: | Height: | Size: 6.4 MiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB |
After Width: | Height: | Size: 35 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB |
|
@ -676,6 +676,7 @@ const tagMedia = [
|
||||||
['blowbang', 'zaawaadi_roccosiffredi_1', 'Zaawaadi in "My Name Is Zaawaadi"', 'roccosiffredi'],
|
['blowbang', 'zaawaadi_roccosiffredi_1', 'Zaawaadi in "My Name Is Zaawaadi"', 'roccosiffredi'],
|
||||||
['blowbang', 1, 'Nicole Black in GIO1680', 'analvids'],
|
['blowbang', 1, 'Nicole Black in GIO1680', 'analvids'],
|
||||||
['blowbang', 'gina_gerson_assholefever', 'Gina Gerson in "Oppa Gangbang Style"', 'assholefever'],
|
['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', 'clanddi_jinkcego_ddfbusty_1', 'Clanddi Jinkcego', 'ddfbusty'],
|
||||||
['blowjob', 'juelz_ventura_babygotboobs', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'],
|
['blowjob', 'juelz_ventura_babygotboobs', 'Juelz Ventura in "A Deep DP For Dessert"', 'babygotboobs'],
|
||||||
['blowjob', 4, 'Chloe Cherry in "Chloe\'s Big Anal"', 'darkx'],
|
['blowjob', 4, 'Chloe Cherry in "Chloe\'s Big Anal"', 'darkx'],
|
||||||
|
|
216
src/alerts.js
|
@ -1,5 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const escapeRegexp = require('escape-string-regexp');
|
||||||
|
|
||||||
const knex = require('./knex');
|
const knex = require('./knex');
|
||||||
const bulkInsert = require('./utils/bulk-insert');
|
const bulkInsert = require('./utils/bulk-insert');
|
||||||
const { HttpError } = require('./errors');
|
const { HttpError } = require('./errors');
|
||||||
|
@ -9,10 +11,14 @@ async function addAlert(alert, sessionUser) {
|
||||||
throw new HttpError('You are not authenthicated', 401);
|
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);
|
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')
|
const [alertId] = await knex('alerts')
|
||||||
.insert({
|
.insert({
|
||||||
user_id: sessionUser.id,
|
user_id: sessionUser.id,
|
||||||
|
@ -30,6 +36,11 @@ async function addAlert(alert, sessionUser) {
|
||||||
alert_id: alertId,
|
alert_id: alertId,
|
||||||
tag_id: tagId,
|
tag_id: tagId,
|
||||||
})), false),
|
})), 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.stashes?.length > 0 && bulkInsert('alerts_stashes', alert.stashes.map((stashId) => ({
|
||||||
alert_id: alertId,
|
alert_id: alertId,
|
||||||
stash_id: stashId,
|
stash_id: stashId,
|
||||||
|
@ -48,88 +59,153 @@ async function removeAlert(alertId) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function notify(scenes) {
|
async function notify(scenes) {
|
||||||
const releases = await knex.raw(`
|
const sceneIds = scenes.map((scene) => scene.id);
|
||||||
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
|
|
||||||
|
|
||||||
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.*
|
const actorIdsByReleaseId = releasesActors.reduce((acc, releaseActor) => {
|
||||||
FROM entities
|
if (!acc[releaseActor.release_id]) {
|
||||||
INNER JOIN included ON included.id = entities.parent_id
|
acc[releaseActor.release_id] = [];
|
||||||
)
|
}
|
||||||
|
|
||||||
SELECT included.id
|
acc[releaseActor.release_id].push(releaseActor.actor_id);
|
||||||
FROM included
|
|
||||||
GROUP BY included.id
|
return acc;
|
||||||
))))
|
}, {});
|
||||||
GROUP BY releases.id, users.id, alerts.id;
|
|
||||||
`, {
|
const tagIdsByReleaseId = releasesTags.reduce((acc, releaseTag) => {
|
||||||
sceneIds: scenes.map((scene) => scene.id),
|
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'),
|
||||||
});
|
});
|
||||||
|
|
||||||
const notifications = releases.rows
|
return acc;
|
||||||
.filter((alert) => alert.notify)
|
}, {});
|
||||||
.map((notification) => ({
|
|
||||||
user_id: notification.user_id,
|
console.log(alertsStashesByAlertId);
|
||||||
alert_id: notification.alert_id,
|
|
||||||
scene_id: notification.scene_id,
|
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 stashes = releases.rows
|
const curatedScenes = scenes.map((scene) => ({
|
||||||
.filter((release) => release.stashes.length > 0)
|
id: scene.id,
|
||||||
.flatMap((release) => release.stashes.map((stash) => ({
|
title: scene.title,
|
||||||
scene_id: release.scene_id,
|
description: scene.description,
|
||||||
stash_id: stash,
|
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 = 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,
|
||||||
|
}));
|
||||||
|
|
||||||
|
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([
|
await Promise.all([
|
||||||
bulkInsert('notifications', notifications, false),
|
bulkInsert('notifications', notifications, false),
|
||||||
bulkInsert('stashes_scenes', stashes, false),
|
bulkInsert('stashes_scenes', stashes, false),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return releases.rows;
|
return triggers;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateNotification(notificationId, notification, sessionUser) {
|
async function updateNotification(notificationId, notification, sessionUser) {
|
||||||
|
|
|
@ -32,7 +32,7 @@ function scrapeAll(scenes, site, entryIdFromTitle) {
|
||||||
|
|
||||||
release.title = title?.slice(0, title.match(/starring:/i)?.index || Infinity).trim();
|
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.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];
|
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 = {};
|
const release = {};
|
||||||
|
|
||||||
release.title = query.text('.overlay-text', { join: false })?.[0];
|
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) => ({
|
release.actors = query.all('.update_models a').map((actorEl) => ({
|
||||||
name: unprint.query.content(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.description = query.content('.update_description') || query.text('//div[./span[contains(text(), "Description")]]');
|
||||||
release.entryId = context.entity.parameters?.entryIdFromTitle ? slugify(release.title) : getEntryId(html);
|
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) => ({
|
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),
|
name: unprint.query.content(actorEl),
|
||||||
|
|