Skip to content

Commit 647211d

Browse files
committed
feat: send keypresses
1 parent e5c3772 commit 647211d

4 files changed

Lines changed: 109 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ Completed features so far:
3030
- [x] Group - Remove Stereo Member
3131
- [x] Group - Update Name
3232
- [x] Introspect Data
33-
- [ ] Key Press, Release
33+
- [x] Key Press, Release
3434
- [ ] Language
3535
- [ ] Media Servers List
3636
- [ ] Music Library - Get Items

src/client.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
22
import { XMLParser, XMLBuilder } from "fast-xml-parser";
3-
import { AudioDSPControls, AudioDSPMode, Bass, BassCapabilities, BluetoothInfo, Capabilities, ClockDisplay, ClockTime, ConfigurationStatus, DeviceInfo, DSPMonoStereo, Group, IntrospectRequest, RemoveGroup, SpotifyAccountIntrospectResponse } from "./types.js";
3+
import { AudioDSPControls, AudioDSPMode, Bass, BassCapabilities, BluetoothInfo, Capabilities, ClockDisplay, ClockTime, ConfigurationStatus, DeviceInfo, DSPMonoStereo, Group, IntrospectRequest, Key, KeyPressResponse, KeyPressState, KeySender, RemoveGroup, SpotifyAccountIntrospectResponse } from "./types.js";
44

55
export class SoundTouchApiClient {
66
private client: AxiosInstance;
@@ -264,4 +264,20 @@ export class SoundTouchApiClient {
264264

265265
return this.parseResponse<SpotifyAccountIntrospectResponse>(res as any);
266266
}
267+
268+
/**
269+
* Get Introspect Data for the current Playback
270+
* @todo validate other Introspect responses
271+
* @returns void
272+
*/
273+
async sendKeyPress(key: Key, state: KeyPressState, sender: KeySender): Promise<KeyPressResponse> {
274+
const xml = `<key state="${state}" sender="${sender}">${key}</key>`;
275+
const res = await this.client.post(this.baseURL + "/key", xml, {
276+
headers: { "Content-Type": "application/xml" },
277+
});
278+
279+
return this.parseResponse<KeyPressResponse>(res as any);
280+
}
281+
282+
267283
}

src/types.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,4 +234,53 @@ export enum IntrospectSource {
234234
Spotify = "SPOTIFY",
235235
Pandora = "PANDORA",
236236
STORED_MUSIC = "STORED_MUSIC",
237+
}
238+
239+
export enum Key {
240+
AddFavorite = "ADD_FAVORITE",
241+
AuxInput = "AUX_INPUT",
242+
Bookmark = "BOOKMARK",
243+
Mute = "MUTE",
244+
NextTrack = "NEXT_TRACK",
245+
Pause = "PAUSE",
246+
Play = "PLAY",
247+
PlayPause = "PLAY_PAUSE",
248+
Power = "POWER",
249+
Preset1 = "PRESET_1",
250+
Preset2 = "PRESET_2",
251+
Preset3 = "PRESET_3",
252+
Preset4 = "PRESET_4",
253+
Preset5 = "PRESET_5",
254+
Preset6 = "PRESET_6",
255+
PreviousTrack = "PREV_TRACK",
256+
RemoveFavorite = "REMOVE_FAVORITE",
257+
RepeatAll = "REPEAT_ALL",
258+
RepeatOff = "REPEAT_OFF",
259+
RepeatOne = "REPEAT_ONE",
260+
ShuffleOff = "SHUFFLE_OFF",
261+
ShuffleOn = "SHUFFLE_ON",
262+
Stop = "STOP",
263+
ThumbsDown = "THUMBS_DOWN",
264+
ThumbsUp = "THUMBS_UP",
265+
VolumeDown = "VOLUME_DOWN",
266+
VolumeUp = "VOLUME_UP",
267+
}
268+
269+
export enum KeyPressState {
270+
Press = "press",
271+
Release = "release",
272+
Repeat = "repeat",
273+
}
274+
275+
export enum KeySender {
276+
Gabbo = "Gabbo",
277+
IrRemote = "IrRemote",
278+
Console = "Console",
279+
LightswitchRemote = "LightswitchRemote",
280+
BoselinkRemote = "BoselinkRemote",
281+
Etap = "Etap",
282+
}
283+
284+
export interface KeyPressResponse {
285+
status: string;
237286
}

tests/key.test.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
2+
import nock from "nock";
3+
import { SoundTouchApiClient } from "../src/client.js";
4+
import { IntrospectRequest, IntrospectSource, IntrospectState, Key, KeyPressState, KeySender } from "../src/types.js";
5+
import { mock } from "node:test";
6+
7+
const mockXML_response = `<?xml version="1.0" encoding="UTF-8" ?>
8+
<status>/key</status>`;
9+
10+
describe("SoundTouchAPI /key endpoint", () => {
11+
const baseURL = "http://localhost:8090";
12+
let api: SoundTouchApiClient;
13+
14+
beforeEach(() => {
15+
api = new SoundTouchApiClient(baseURL);
16+
nock.cleanAll();
17+
});
18+
19+
afterEach(() => {
20+
nock.cleanAll();
21+
});
22+
23+
it("parses /key response correctly", async () => {
24+
25+
const expected_request = `<key state="press" sender="Gabbo">POWER</key>`;
26+
27+
28+
nock(baseURL)
29+
.post("/key", expected_request)
30+
.matchHeader("Content-Type", "application/xml")
31+
.reply(200, mockXML_response, { "Content-Type": "application/xml" });
32+
33+
34+
const keySendResponse = await api.sendKeyPress(Key.Power, KeyPressState.Press, KeySender.Gabbo);
35+
// Ensure the mocked endpoint was called
36+
expect(nock.isDone()).toBe(true);
37+
38+
expect(keySendResponse).toMatchObject({
39+
status: "/key",
40+
});
41+
});
42+
});

0 commit comments

Comments
 (0)