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, combos) { if ((keys === undefined || keys.length === 0) && (combos === undefined || combos.length === 0)) { return; } this.actions = new Map(); for (var index = 0; index < keys.length; index++) { this.setAction(keys[index]); } this.currentCombo = undefined; } setAction(config) { var type = ACTION_KEYDOWN; switch (config.type.toLowerCase()) { case ACTION_KEYUP.action: type = ACTION_KEYUP; break; case ACTION_KEYHOLD.action: type = ACTION_KEYHOLD; break; } this.actions.set(config.key.toUpperCase(), { type: type, event: config.event, command: config.command, args: config.args, 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(); var lines = input.split("\n"); for (var index = 0; index < lines.length; index++) { var line = lines[index]; if (line.length === 0) { continue; } if (!line.startsWith(LINE_START)) { continue; } const parsedEvent = this.parseLine(line); if (parsedEvent === undefined) { continue; } for (var [key, event] of this.actions) { if (this.currentCombo === undefined && !this.isParsedEventValid(key, event, parsedEvent)) { continue; } if (this.isStartOfCombo(key, event)) { 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.hasComboFinished()) { 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) { return key === parsed.key && (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 = this.replaceVariables( { key: key, type: event.type.action, command: event.command, args: event.args, 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; } } replaceVariables(filtered) { for (var index = 0; index < filtered.args.length; index++) { filtered.args[index] = filtered.args[index].replace(VARIABLE_KEY, filtered.key); filtered.args[index] = filtered.args[index].replace(VARIABLE_TYPE, filtered.type); } return filtered; } 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) { if (this.currentCombo !== undefined || event.combo === undefined) { return false; } this.currentCombo = { key: key, type: event.type, start: new Date().getTime(), done: [key], remaining: JSON.parse(JSON.stringify(event.combo)), finished: this.hasComboFinished() }; 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; } if (this.currentCombo.remaining[0].toUpperCase() !== parsedEvent.key) { return false; } this.currentCombo.key = parsedEvent.key; this.currentCombo.done.push(parsedEvent.key); this.currentCombo.remaining.shift(); this.currentCombo.finished = this.hasComboFinished(); return true; } hasComboFinished() { return this.currentCombo !== undefined && this.currentCombo.remaining !== undefined && this.currentCombo.remaining.length === 0; } hasComboTimedOut() { return this.currentCombo !== undefined && this.currentCombo.delay !== undefined && this.currentCombo.start !== undefined && new Date().getTime() - this.currentCombo.start > this.currentCombo.delay; } resetCurrentCombo() { this.currentCombo = undefined; } isValid() { return this.actions != undefined && this.actions.size > 0; } } module.exports = Keyfilter;