const config = require('../config.json'); const LINE_START = 'Event: time'; const ACTION_KEYUP = { id: 0, action: 'keyup' }; const ACTION_KEYDOWN = { id: 1, action: 'keydown' }; const ACTION_KEYHOLD = { id: 2, action: 'keyhold' }; const VARIABLE_KEY = '{{ key }}'; const VARIABLE_TYPE = '{{ type }}'; class Keyfilter { constructor(keys) { if ((keys === undefined || keys.length === 0)) { return; } this.actions = new Map(); for (let index = 0; index < keys.length; index++) { this.setAction(keys[index]); } this.currentCombo = undefined; } setAction(config) { let type = ACTION_KEYDOWN; switch (config.type.toLowerCase()) { case ACTION_KEYUP.action: type = ACTION_KEYUP; break; case ACTION_KEYHOLD.action: type = ACTION_KEYHOLD; break; } let key = config.key.toUpperCase(); if (config.combo !== undefined && config.combo.length > 0) { const tmp = JSON.parse(JSON.stringify(config.combo)); tmp.unshift(config.key); key = tmp.toString().toUpperCase(); } this.actions.set(key, { type: type, event: config.event, command: getCommand(config.command), combo: config.combo, delay: function () { if (config.combo === undefined) { return config.delay; } return config.delay || 1000; }(), } ); } filter(input) { if (input === undefined || input.length === 0) { return; } input = input.toString(); let lines = input.split("\n"); for (let index = 0; index < lines.length; index++) { let line = lines[index]; if (line.length === 0) { continue; } if (!line.startsWith(LINE_START)) { continue; } const parsedEvent = this.parseLine(line); if (parsedEvent === undefined) { continue; } for (let [key, event] of this.actions) { if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) { continue; } if (this.isStartOfCombo(key, event, parsedEvent)) { return this.getFilterResult(key, event, 'combo', this.currentCombo); } if (this.isPartOfCombo(parsedEvent)) { if (parsedEvent.ignore) { continue; } const result = this.getFilterResult(key, event, 'combo', this.currentCombo); if (this.currentCombo.finished) { this.resetCurrentCombo(); } return result; } if (!this.isParsedEventValid(key, event, parsedEvent)) { continue; } if (this.shouldBeDelayed(event)) { return this.getFilterResult(key, event, 'delayed', true); } event.captured = new Date().getTime(); return this.getFilterResult(key, event); } } } isParsedEventValid(key, value, parsed) { let keyCheck = key === parsed.key; if (value.combo !== undefined && value.combo.length > 0) { keyCheck = key.includes(parsed.key); } return keyCheck && (value.event === undefined || value.event === parsed.event) && value.type.id === parsed.type; } getFilterResult(key, event, extraName, extra) { if (key === undefined || event === undefined) { return; } let result = { key: key, type: event.type.action, command: event.command, delay: event.delay }; if (extraName !== undefined && extra !== undefined) { result[extraName] = extra; } return result; } parseLine(line) { try { const parts = line.split(','); const event = parts[1].substring(parts[1].indexOf('(') + 1, parts[1].lastIndexOf(')')); const key = parts[2].substring(parts[2].indexOf('(') + 1, parts[2].indexOf(')')); const type = parseInt(parts[3].split(' ').pop()); return { event: event, key: key, type: type }; } catch (err) { return; } } shouldBeDelayed(event) { if (event.delay === undefined || event.delay === 0 || event.captured === undefined) { return false; } return new Date().getTime() - event.captured < event.delay; } isStartOfCombo(key, event, parsedEvent) { if (event.combo === undefined || this.currentCombo !== undefined) { return false; } let possibilities = []; for (let [actionKey, actionEvent] of this.actions) { if (actionEvent.combo === undefined || actionEvent.combo.length === 0) { continue; } if (!actionKey.toUpperCase().startsWith(parsedEvent.key)) { continue; } possibilities.push(actionEvent.combo); } this.currentCombo = { key: key, type: event.type, delay: event.delay, timestamp: new Date().getTime(), done: [parsedEvent.key], possibilities: [event.combo] }; return true; } isPartOfCombo(parsedEvent) { if (this.hasComboTimedOut()) { this.resetCurrentCombo(); } if (this.currentCombo === undefined) { return false; } if (this.currentCombo.done.includes(parsedEvent.key)) { parsedEvent.ignore = true; return true; } if (this.currentCombo.type.id !== parsedEvent.type) { return false; } let combos = []; for (let index = 0; index < this.currentCombo.possibilities.length; index++) { const possibility = this.currentCombo.possibilities[index]; if (possibility[0].toUpperCase() !== parsedEvent.key) { continue; } combos.push(possibility); } if (combos.length === 0) { return false; } this.currentCombo.key = parsedEvent.key; this.currentCombo.timestamp = new Date().getTime(); this.currentCombo.done.push(parsedEvent.key); if (this.currentCombo.possibilities.length === 1) { this.currentCombo.finished = true; } return true; } hasComboTimedOut() { return false; const result = this.currentCombo !== undefined && this.currentCombo.delay !== undefined && this.currentCombo.timestamp !== undefined && new Date().getTime() - this.currentCombo.timestamp > this.currentCombo.delay; return result; } resetCurrentCombo() { this.currentCombo = undefined; } isValid() { return this.actions != undefined && this.actions.size > 0; } } function getCommand(command) { if (command === undefined || config.commands === undefined) { return; } const result = config.commands[command]; if (result === undefined) { return; } result.name = command; return result; } module.exports = Keyfilter;