@raphiiko/wavelink-cli 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -29
- package/dist/index.js +4891 -4793
- package/package.json +57 -57
- package/src/commands/channel.ts +243 -0
- package/src/commands/info.ts +19 -0
- package/src/commands/input.ts +128 -0
- package/src/commands/mix.ts +98 -0
- package/src/commands/output.ts +207 -0
- package/src/index.ts +14 -754
- package/src/services/client.ts +24 -0
- package/src/services/finders.ts +150 -0
- package/src/types/index.ts +23 -0
- package/src/utils/error.ts +4 -0
- package/src/utils/format.ts +15 -0
- package/src/utils/validation.ts +9 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { WaveLinkClient } from "@raphiiko/wavelink-ts";
|
|
2
|
+
|
|
3
|
+
export async function withClient<T>(action: (client: WaveLinkClient) => Promise<T>): Promise<T> {
|
|
4
|
+
const client = new WaveLinkClient({
|
|
5
|
+
autoReconnect: false,
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
console.log("Connecting to Wave Link...");
|
|
10
|
+
await client.connect();
|
|
11
|
+
console.log("Connected successfully");
|
|
12
|
+
|
|
13
|
+
return await action(client);
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error instanceof Error) {
|
|
16
|
+
console.error(`Error: ${error.message}`);
|
|
17
|
+
} else {
|
|
18
|
+
console.error("An unexpected error occurred");
|
|
19
|
+
}
|
|
20
|
+
process.exit(1);
|
|
21
|
+
} finally {
|
|
22
|
+
client.disconnect();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { WaveLinkClient } from "@raphiiko/wavelink-ts";
|
|
2
|
+
import type { MixInfo, OutputInfo, InputInfo, ChannelInfo } from "../types/index.js";
|
|
3
|
+
import { exitWithError } from "../utils/error.js";
|
|
4
|
+
import { getChannelName } from "../utils/format.js";
|
|
5
|
+
|
|
6
|
+
export async function findMixByIdOrName(
|
|
7
|
+
client: WaveLinkClient,
|
|
8
|
+
idOrName: string
|
|
9
|
+
): Promise<MixInfo | null> {
|
|
10
|
+
const { mixes } = await client.getMixes();
|
|
11
|
+
const lowerIdOrName = idOrName.toLowerCase();
|
|
12
|
+
const mix = mixes.find(
|
|
13
|
+
(m) => m.id.toLowerCase() === lowerIdOrName || (m.name || "").toLowerCase() === lowerIdOrName
|
|
14
|
+
);
|
|
15
|
+
return mix ? { id: mix.id, name: mix.name || mix.id } : null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function findChannelByIdOrName(
|
|
19
|
+
client: WaveLinkClient,
|
|
20
|
+
idOrName: string
|
|
21
|
+
): Promise<ChannelInfo | null> {
|
|
22
|
+
const { channels } = await client.getChannels();
|
|
23
|
+
const lowerIdOrName = idOrName.toLowerCase();
|
|
24
|
+
|
|
25
|
+
// First try exact ID match
|
|
26
|
+
const channelById = channels.find((c) => c.id.toLowerCase() === lowerIdOrName);
|
|
27
|
+
if (channelById) {
|
|
28
|
+
return { id: channelById.id, name: getChannelName(channelById) };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Then try name match (checking both channel.name and channel.image.name)
|
|
32
|
+
const channelByName = channels.find((c) => {
|
|
33
|
+
const name = getChannelName(c);
|
|
34
|
+
return name.toLowerCase() === lowerIdOrName;
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return channelByName ? { id: channelByName.id, name: getChannelName(channelByName) } : null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function findInputByIdOrName(
|
|
41
|
+
client: WaveLinkClient,
|
|
42
|
+
idOrName: string
|
|
43
|
+
): Promise<InputInfo | null> {
|
|
44
|
+
const { inputDevices } = await client.getInputDevices();
|
|
45
|
+
const lowerIdOrName = idOrName.toLowerCase();
|
|
46
|
+
|
|
47
|
+
// First try exact ID match
|
|
48
|
+
for (const device of inputDevices) {
|
|
49
|
+
const input = device.inputs.find((i) => i.id.toLowerCase() === lowerIdOrName);
|
|
50
|
+
if (input) {
|
|
51
|
+
return {
|
|
52
|
+
deviceId: device.id,
|
|
53
|
+
deviceName: device.name || device.id,
|
|
54
|
+
inputId: input.id,
|
|
55
|
+
inputName: input.name || input.id,
|
|
56
|
+
gain: input.gain.value,
|
|
57
|
+
isMuted: input.isMuted,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Then try name match
|
|
63
|
+
for (const device of inputDevices) {
|
|
64
|
+
const input = device.inputs.find((i) => (i.name || "").toLowerCase() === lowerIdOrName);
|
|
65
|
+
if (input) {
|
|
66
|
+
return {
|
|
67
|
+
deviceId: device.id,
|
|
68
|
+
deviceName: device.name || device.id,
|
|
69
|
+
inputId: input.id,
|
|
70
|
+
inputName: input.name || input.id,
|
|
71
|
+
gain: input.gain.value,
|
|
72
|
+
isMuted: input.isMuted,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function findOutputByIdOrName(
|
|
81
|
+
client: WaveLinkClient,
|
|
82
|
+
idOrName: string
|
|
83
|
+
): Promise<OutputInfo | null> {
|
|
84
|
+
const { outputDevices } = await client.getOutputDevices();
|
|
85
|
+
const lowerIdOrName = idOrName.toLowerCase();
|
|
86
|
+
|
|
87
|
+
// First try exact ID match
|
|
88
|
+
for (const device of outputDevices) {
|
|
89
|
+
const output = device.outputs.find((o) => o.id.toLowerCase() === lowerIdOrName);
|
|
90
|
+
if (output) {
|
|
91
|
+
return {
|
|
92
|
+
deviceId: device.id,
|
|
93
|
+
outputId: output.id,
|
|
94
|
+
currentMixId: output.mixId,
|
|
95
|
+
deviceName: device.name || device.id,
|
|
96
|
+
isWaveDevice: device.isWaveDevice,
|
|
97
|
+
outputName: output.name || output.id,
|
|
98
|
+
level: output.level,
|
|
99
|
+
isMuted: output.isMuted,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Then try name match
|
|
105
|
+
for (const device of outputDevices) {
|
|
106
|
+
const output = device.outputs.find((o) => (o.name || "").toLowerCase() === lowerIdOrName);
|
|
107
|
+
if (output) {
|
|
108
|
+
return {
|
|
109
|
+
deviceId: device.id,
|
|
110
|
+
outputId: output.id,
|
|
111
|
+
currentMixId: output.mixId,
|
|
112
|
+
deviceName: device.name || device.id,
|
|
113
|
+
isWaveDevice: device.isWaveDevice,
|
|
114
|
+
outputName: output.name || output.id,
|
|
115
|
+
level: output.level,
|
|
116
|
+
isMuted: output.isMuted,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Required finder wrappers that exit on not found
|
|
125
|
+
export async function requireMix(client: WaveLinkClient, mixId: string): Promise<MixInfo> {
|
|
126
|
+
const mix = await findMixByIdOrName(client, mixId);
|
|
127
|
+
if (!mix) exitWithError(`Mix '${mixId}' not found`);
|
|
128
|
+
return mix;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export async function requireOutput(client: WaveLinkClient, outputId: string): Promise<OutputInfo> {
|
|
132
|
+
const output = await findOutputByIdOrName(client, outputId);
|
|
133
|
+
if (!output) exitWithError(`Output '${outputId}' not found`);
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function requireInput(client: WaveLinkClient, inputId: string): Promise<InputInfo> {
|
|
138
|
+
const input = await findInputByIdOrName(client, inputId);
|
|
139
|
+
if (!input) exitWithError(`Input '${inputId}' not found`);
|
|
140
|
+
return input;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function requireChannel(
|
|
144
|
+
client: WaveLinkClient,
|
|
145
|
+
channelId: string
|
|
146
|
+
): Promise<ChannelInfo> {
|
|
147
|
+
const channel = await findChannelByIdOrName(client, channelId);
|
|
148
|
+
if (!channel) exitWithError(`Channel '${channelId}' not found`);
|
|
149
|
+
return channel;
|
|
150
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type MixInfo = { id: string; name: string };
|
|
2
|
+
|
|
3
|
+
export type OutputInfo = {
|
|
4
|
+
deviceId: string;
|
|
5
|
+
outputId: string;
|
|
6
|
+
currentMixId: string;
|
|
7
|
+
deviceName: string;
|
|
8
|
+
isWaveDevice: boolean;
|
|
9
|
+
outputName: string;
|
|
10
|
+
level: number;
|
|
11
|
+
isMuted: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type InputInfo = {
|
|
15
|
+
deviceId: string;
|
|
16
|
+
deviceName: string;
|
|
17
|
+
inputId: string;
|
|
18
|
+
inputName: string;
|
|
19
|
+
gain: number;
|
|
20
|
+
isMuted: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type ChannelInfo = { id: string; name: string };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function formatPercent(value: number): string {
|
|
2
|
+
return `${(value * 100).toFixed(0)}%`;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function formatMuted(isMuted: boolean): string {
|
|
6
|
+
return isMuted ? "Yes" : "No";
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function getChannelName(channel: {
|
|
10
|
+
id: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
image?: { name?: string };
|
|
13
|
+
}): string {
|
|
14
|
+
return channel.name ?? channel.image?.name ?? channel.id;
|
|
15
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { exitWithError } from "./error.js";
|
|
2
|
+
|
|
3
|
+
export function parsePercent(value: string, name: string): number {
|
|
4
|
+
const percent = parseInt(value, 10);
|
|
5
|
+
if (isNaN(percent) || percent < 0 || percent > 100) {
|
|
6
|
+
exitWithError(`${name} must be a number between 0 and 100`);
|
|
7
|
+
}
|
|
8
|
+
return percent;
|
|
9
|
+
}
|