mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-11 10:23:54 +00:00
141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
import {
|
|
type AutomationFilter,
|
|
type AutomationOutput,
|
|
type FilterRule,
|
|
LogOrigin,
|
|
RuntimeStore,
|
|
TimerLifeCycle,
|
|
isHTTPOutput,
|
|
isOSCOutput,
|
|
isOntimeAction,
|
|
} from 'ontime-types';
|
|
import { getPropertyFromPath } from 'ontime-utils';
|
|
|
|
import { logger } from '../../classes/Logger.js';
|
|
import { isOntimeCloud } from '../../setup/environment.js';
|
|
import { eventStore } from '../../stores/EventStore.js';
|
|
import { getAutomationTriggers, getAutomations, getAutomationsEnabled } from './automation.dao.js';
|
|
import { isContained, isEquivalent, isGreaterThan, isLessThan } from './automation.utils.js';
|
|
import { emitHTTP } from './clients/http.client.js';
|
|
import { toOntimeAction } from './clients/ontime.client.js';
|
|
import { emitOSC } from './clients/osc.client.js';
|
|
|
|
/**
|
|
* Exposes a method for triggering actions based on a TimerLifeCycle event
|
|
*/
|
|
export function triggerAutomations(cycle: TimerLifeCycle) {
|
|
if (!getAutomationsEnabled()) {
|
|
return;
|
|
}
|
|
const store = eventStore.poll();
|
|
|
|
let triggers = getAutomationTriggers();
|
|
|
|
// get triggers from event
|
|
if (store.eventNow?.triggers) {
|
|
triggers = triggers.concat(store.eventNow.triggers);
|
|
}
|
|
|
|
// note: there are no onStop triggers in event
|
|
const filteredTrigger = triggers.filter((trigger) => trigger.trigger === cycle);
|
|
if (filteredTrigger.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const automations = getAutomations();
|
|
if (Object.keys(automations).length === 0) {
|
|
return;
|
|
}
|
|
|
|
// deduplicate — if the same automation appears multiple times on one lifecycle, fire it once
|
|
const seen = new Set<string>();
|
|
const uniqueTriggers = filteredTrigger.filter((t) => {
|
|
if (seen.has(t.automationId)) return false;
|
|
seen.add(t.automationId);
|
|
return true;
|
|
});
|
|
|
|
uniqueTriggers.forEach((trigger) => {
|
|
const automation = automations[trigger.automationId];
|
|
if (!automation || automation.outputs.length === 0) {
|
|
return;
|
|
}
|
|
const shouldSend = testConditions(automation.filters, automation.filterRule, store);
|
|
if (shouldSend) {
|
|
send(automation.outputs, store);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Exposes a method for bypassing the condition check and testing the sending of an output
|
|
*/
|
|
export function testOutput(payload: AutomationOutput) {
|
|
const store = eventStore.poll();
|
|
send([payload], store);
|
|
}
|
|
|
|
/**
|
|
* Checks whether the automation conditions are met
|
|
*/
|
|
export function testConditions(
|
|
filters: AutomationFilter[],
|
|
filterRule: FilterRule,
|
|
store: Partial<RuntimeStore>,
|
|
): boolean {
|
|
if (filters.length === 0) {
|
|
return true;
|
|
}
|
|
|
|
if (filterRule === 'all') {
|
|
return filters.every(evaluateCondition);
|
|
}
|
|
|
|
return filters.some(evaluateCondition);
|
|
|
|
function evaluateCondition(filter: AutomationFilter): boolean {
|
|
const { field, operator, value } = filter;
|
|
const lowerCasedValue = value.toLowerCase();
|
|
const fieldValue = getPropertyFromPath(field, store);
|
|
|
|
// if value is empty string, the user could be meaning to check if the value does not exist
|
|
// we use loose equality to be able to check for converted values (eg '10' == 10)
|
|
switch (operator) {
|
|
case 'equals':
|
|
return isEquivalent(fieldValue, lowerCasedValue);
|
|
case 'not_equals':
|
|
return !isEquivalent(fieldValue, lowerCasedValue);
|
|
case 'greater_than':
|
|
return isGreaterThan(fieldValue, value);
|
|
case 'less_than':
|
|
return isLessThan(fieldValue, value);
|
|
case 'contains':
|
|
return isContained(fieldValue, lowerCasedValue);
|
|
case 'not_contains':
|
|
return !isContained(fieldValue, lowerCasedValue);
|
|
default: {
|
|
operator satisfies never;
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handles preparing and sending of the data
|
|
* Returns a boolean indicating whether a message was sent
|
|
*/
|
|
function send(output: AutomationOutput[], store: RuntimeStore) {
|
|
output.forEach((payload) => {
|
|
if (isOSCOutput(payload) && !isOntimeCloud) {
|
|
emitOSC(payload, store);
|
|
} else if (isHTTPOutput(payload)) {
|
|
emitHTTP(payload, store);
|
|
} else if (isOntimeAction(payload)) {
|
|
toOntimeAction(payload);
|
|
} else {
|
|
logger.warning(LogOrigin.Tx, `Unknown output type: ${payload}`);
|
|
}
|
|
});
|
|
}
|