'use strict'
const BUTTONS = {
home: 4,
power: 8,
'volume-up': 16,
'volume-down': 32,
ringer: 64
}
function buttonId (button) {
if (!(button in BUTTONS)) throw new Error(`invalid button ${button}`)
return BUTTONS[button]
}
const TOUCH = 1
/**
* An input to send to an instance.
*
* Inputs consist of a series of steps. Each method in this class adds a step
* to the current input, and returns the current input to allow chaining. To
* run the input, send it to an instance with {@link Instance#sendInput}.
*
* A global, `{@link I}`, is provided as a convenience. `I.doThing()` is a
* shorthand for `new Input().doThing()`.
*
* @see {@link Instance#sendInput}
* @example
* const input = new Input().pressRelease('home').tap(100, 100);
* await instance.sendInput(input);
* // using the I shortcut
* const input2 = I.pressRelease('home').tap(100, 100);
* await instance.sendInput(input2);
*/
class Input {
/**
* The name of a button. Possible values are:
*
* Button|Description
* -|-
* `'home'`|Home button
* `'power'`|Power button
* `'volume-up'`|Volume up button
* `'volume-down'`|Volume down button
* `'ringer'`|Ringer switch
* @typedef {string} Input~ButtonName
*/
/**
* Creates a new input with no steps. It's usually more convenient to use
* `{@link I}` instead.
*/
constructor () {
this.points = []
this.pressed = 0
this._delay = 0
}
_addPoint (point = {}) {
point.buttons = this.pressed
if (this._delay !== 0) point.delay = this._delay
this.points.push(point)
this._delay = 0
return this
}
/**
* Add a step to press and hold the specified buttons.
* @param {...Input~ButtonName} buttonNames - Names of buttons to press.
* @returns this
*/
press (...buttonNames) {
buttonNames.forEach((button) => {
this.pressed |= buttonId(button)
})
return this._addPoint()
}
/**
* Add a step to release the specified buttons.
* @param {...Input~ButtonName} buttonNames - Names of buttons to release.
* @returns this
*/
release (...buttonNames) {
buttonNames.forEach((button) => {
this.pressed &= ~buttonId(button)
})
return this._addPoint()
}
/**
* Add a step to delay by the specified number of milliseconds.
* @param {number} [delay=100] - The number of milliseconds to delay.
* @returns this
*/
delay (delay) {
this._delay = delay
return this
}
/**
* Add steps to press the specified button, delay for an interval
* defaulting to 100 milliseconds, and release the specified button.
* @param {Input~ButtonName} button - The button to press and release.
* @param {number} [delay=100] The number of milliseconds to hold down the button.
* @returns this
*/
pressRelease (button, delay = 100) {
return this.press(button).delay(delay).release(button)
}
/**
* Add a step to set the current touch position and start touching the screen.
* @param {number} x - The x coordinate.
* @param {number} y - The y coordinate.
* @return this
*/
touch (x, y) {
this.pressed |= TOUCH
return this._addPoint({ pos: [x, y] })
}
/**
* Add a step to release the touchscreen.
* @return this
*/
touchUp () {
this.pressed &= ~TOUCH
return this._addPoint()
}
/**
* Add a step to swipe from the current touch position to the specified
* position. Bezier control points may be specified. If there is a delay
* step immediately before this one, the swipe will take place over the
* delay; otherwise it will happen instantly.
* @param {number} x - The x coordinate to swipe to.
* @param {number} y - The y coordinate to swipe to.
* @param {point[]} [curve] - An array of Bezier control points. Each
* point is a two-element array containing an x coordinate and a y
* coordinate.
*/
swipeTo (x, y, curve) {
if (!(this.pressed & TOUCH)) throw new Error('touch must be down to swipe')
return this._addPoint({ pos: [x, y], curve })
}
/**
* Add steps to touch the screen at the given position, delay for an
* interval defaulting to 100 milliseconds, and release the touchscreen.
* @param {number} x - The x coordinate.
* @param {number} y - The y coordinate.
* @param {number} [delay=100] The number of milliseconds to hold down the touch.
*/
tap (x, y, delay = 100) {
return this.touch(x, y).delay(delay).touchUp()
}
}
/**
* A magic object that can be used as a shortcut for `new Input()` in
* expressions like `new Input().pressRelease('home')`.
* @see Input
* @constant
* @example
* I.press('home').delay(250).release('home')
* I.touch(100, 100).delay(250).swipeTo(200, 200).touchUp()
*/
const I = new Proxy(
{},
{
get (_target, prop) {
return function (...args) {
return new Input()[prop](...args)
}
}
}
)
module.exports = { Input, I }