change from poll to update from hooks
This commit is contained in:
12
.env.example
12
.env.example
@@ -1,11 +1 @@
|
||||
API_KEY=apiKey
|
||||
PORT=
|
||||
ENV_DEV=true
|
||||
|
||||
TIDAL_HOST=http://localhost:47836
|
||||
|
||||
HA_API_URL=http://homeassistant.com/api/states/
|
||||
HA_API_TOKEN=Nina hätte hier jetzt ihr Token ausversehen stehen hihi
|
||||
HA_ROOMTEMP_SENSOR_IDS=entityId(,separated)
|
||||
HA_STANDING_WEBHOOK=webhookId
|
||||
HA_DESK_SENSOR_BINARY=entityId
|
||||
look into config.ts
|
||||
|
||||
26
package-lock.json
generated
26
package-lock.json
generated
@@ -30,8 +30,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@dpu/shared": {
|
||||
"version": "1.9.1",
|
||||
"resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#2b85020d52e9f79956e2fde3833eef071f6b73af",
|
||||
"version": "1.9.6",
|
||||
"resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#872d3755d06c8e8c6452d8ab7212b6426d84943b",
|
||||
"dependencies": {
|
||||
"@types/ws": "^8.18.1",
|
||||
"axios": "^1.7.9",
|
||||
@@ -679,9 +679,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fastify/swagger": {
|
||||
"version": "9.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.6.1.tgz",
|
||||
"integrity": "sha512-fKlpJqFMWoi4H3EdUkDaMteEYRCfQMEkK0HJJ0eaf4aRlKd8cbq0pVkOfXDXmtvMTXYcnx3E+l023eFDBsA1HA==",
|
||||
"version": "9.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@fastify/swagger/-/swagger-9.7.0.tgz",
|
||||
"integrity": "sha512-Vp1SC1GC2Hrkd3faFILv86BzUNyFz5N4/xdExqtCgkGASOzn/x+eMe4qXIGq7cdT6wif/P/oa6r1Ruqx19paZA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -790,9 +790,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.10.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.11.tgz",
|
||||
"integrity": "sha512-/Af7O8r1frCVgOz0I62jWUtMohJ0/ZQU/ZoketltOJPZpnb17yoNc9BSoVuV9qlaIXJiPNOpsfq4ByFajSArNQ==",
|
||||
"version": "24.10.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.12.tgz",
|
||||
"integrity": "sha512-68e+T28EbdmLSTkPgs3+UacC6rzmqrcWFPQs1C8mwJhI/r5Uxr0yEuQotczNRROd1gq30NGxee+fo0rSIxpyAw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.16.0"
|
||||
@@ -872,13 +872,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
|
||||
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
|
||||
"version": "1.13.5",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz",
|
||||
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.4",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"clean": "rimraf dist",
|
||||
"build": "npm run clean && tsc",
|
||||
"start": "node dist/index.js",
|
||||
"dev": "tsx watch --inspect-brk src/index.ts"
|
||||
"dev": "tsx watch src/index.ts",
|
||||
"devdbg": "tsx watch --inspect-brk src/index.ts"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Darius",
|
||||
|
||||
@@ -19,7 +19,7 @@ export const Config = {
|
||||
api_token: process.env.HA_API_TOKEN || "",
|
||||
|
||||
id_sensor_desk_binary: process.env.HA_SENSOR_DESK_BINARY || "",
|
||||
id_sensors_roomtemp: process.env.HA_SENSORS_ROOMTEMP?.split(",") || [],
|
||||
id_sensor_roomtemp: process.env.HA_SENSOR_ROOMTEMP || "",
|
||||
|
||||
id_webhook_stand: process.env.HA_STANDING_WEBHOOK || "",
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {
|
||||
BaseService,
|
||||
type GristRecord_PersonalGoals,
|
||||
logWarning,
|
||||
type ServiceResult,
|
||||
} from "@dpu/shared";
|
||||
import { DateTime } from "luxon";
|
||||
@@ -25,14 +24,12 @@ export class GristService extends BaseService<GristClient> {
|
||||
this.transformToPersonalGoals(response.records[0].fields),
|
||||
);
|
||||
} else {
|
||||
const error_message = "error finding record from grist";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult(
|
||||
"error getting record from grist. record not found.",
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
const error_message = "error getting record from grist";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting record from grist.", error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,16 +2,6 @@ import { BaseClient, type HomeAssistantEntity } from "@dpu/shared";
|
||||
import { printNetworkError } from "@dpu/shared/dist/logger.js";
|
||||
|
||||
export class HomeAssistantClient extends BaseClient {
|
||||
async getEntityStates(entityIds: string[]): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
const promises = entityIds.map((id) => this.getEntityState(id));
|
||||
return await Promise.all(promises);
|
||||
} catch (error) {
|
||||
printNetworkError(error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async getEntityState(entityId: string): Promise<HomeAssistantEntity> {
|
||||
try {
|
||||
const response = await this.getAxios().get<HomeAssistantEntity>(
|
||||
|
||||
@@ -93,7 +93,7 @@ export async function homeAssistantRoutes(
|
||||
},
|
||||
},
|
||||
async (_request, reply) => {
|
||||
const service_result = await haService.getTemperatureText();
|
||||
const service_result = await haService.getTemperature();
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
type HomeAssistantEntity,
|
||||
type ServiceResult,
|
||||
} from "@dpu/shared";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import { calculateSecondsBetween } from "@dpu/shared/dist/timehelper.js";
|
||||
import { Config } from "../config.js";
|
||||
import type { HomeAssistantClient } from "./client.js";
|
||||
@@ -37,9 +36,7 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
const error_message = `error starting stand automation. ${error instanceof Error ? error.message : error}`;
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult("error starting stand automation.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,9 +48,18 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
Config.homeassistant.id_sensor_desk_binary,
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(this.convertHaEntityToPosResult(raw));
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting desk position.", error);
|
||||
}
|
||||
}
|
||||
|
||||
convertHaEntityToPosResult(
|
||||
raw: HomeAssistantEntity,
|
||||
): HomeAssistantDeskPositionResult {
|
||||
const position = Number(raw.state);
|
||||
|
||||
const result = {
|
||||
return {
|
||||
raw,
|
||||
as_boolean: position === 1,
|
||||
last_changed: calculateSecondsBetween(
|
||||
@@ -61,13 +67,6 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
Date.now(),
|
||||
),
|
||||
};
|
||||
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
const error_message = "error getting desk position";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
convertPosResultToApiAnswer(
|
||||
@@ -80,33 +79,15 @@ export class HomeAssistantService extends BaseService<HomeAssistantClient> {
|
||||
};
|
||||
}
|
||||
|
||||
async getTemperatureText(): Promise<ServiceResult<string>> {
|
||||
async getTemperature(): Promise<ServiceResult<string>> {
|
||||
try {
|
||||
const entities = await this.getTemperatures();
|
||||
const values = entities
|
||||
.map((entity) => parseFloat(entity.state))
|
||||
.filter((value) => !Number.isNaN(value));
|
||||
const average =
|
||||
values.length > 0
|
||||
? values.reduce((sum, value) => sum + value, 0) / values.length
|
||||
: 0;
|
||||
const result = average.toFixed(2);
|
||||
return this.getSuccessfulResult(result);
|
||||
} catch (error) {
|
||||
const error_message = "error getting temperature as text";
|
||||
logWarning(error_message, error);
|
||||
return this.getErrorResult(error_message);
|
||||
}
|
||||
}
|
||||
|
||||
private async getTemperatures(): Promise<HomeAssistantEntity[]> {
|
||||
try {
|
||||
return await this.getClient().getEntityStates(
|
||||
Config.homeassistant.id_sensors_roomtemp,
|
||||
const entity = await this.getClient().getEntityState(
|
||||
Config.homeassistant.id_sensor_roomtemp,
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(entity.state);
|
||||
} catch (error) {
|
||||
logWarning("error getting temperatures:", error);
|
||||
return [];
|
||||
return this.getErrorResult("error getting temperature.", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
import type {
|
||||
API_HA_DeskPosition,
|
||||
ComponentUpdate,
|
||||
GristRecord_PersonalGoals,
|
||||
HA_Update,
|
||||
HomeAssistantEntity,
|
||||
TidalGetCurrent,
|
||||
} from "@dpu/shared";
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import type { FastifyInstance, FastifyReply } from "fastify";
|
||||
import type { FastifyRequest } from "fastify/types/request.js";
|
||||
import { z } from "zod";
|
||||
import { Config } from "../config.js";
|
||||
import type { HomeAssistantService } from "../homeassistant/service.js";
|
||||
import type { HomepageService } from "./service.js";
|
||||
|
||||
export async function homepageRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{
|
||||
hpService,
|
||||
haService,
|
||||
verifyAPIKey,
|
||||
}: {
|
||||
hpService: HomepageService;
|
||||
haService: HomeAssistantService;
|
||||
verifyAPIKey: (
|
||||
request: FastifyRequest,
|
||||
reply: FastifyReply,
|
||||
) => Promise<void>;
|
||||
},
|
||||
) {
|
||||
fastify.get(
|
||||
@@ -47,4 +60,96 @@ export async function homepageRoutes(
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/homepage/update/homeassistant",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description: "Update information for component on dpu status page",
|
||||
tags: ["homepage"],
|
||||
body: z.custom<unknown>(),
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
hide: true,
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const ha_update = request.body as HA_Update;
|
||||
const ha_entity: HomeAssistantEntity = {
|
||||
entity_id: ha_update.entity_id,
|
||||
state: ha_update.state,
|
||||
attributes: ha_update.attributes,
|
||||
last_changed: ha_update.timestamp,
|
||||
};
|
||||
const updates: ComponentUpdate[] = [];
|
||||
|
||||
switch (ha_update.entity_id) {
|
||||
case Config.homeassistant.id_sensor_desk_binary: {
|
||||
updates.push({
|
||||
component: "desk",
|
||||
data: haService.convertPosResultToApiAnswer(
|
||||
haService.convertHaEntityToPosResult(ha_entity),
|
||||
),
|
||||
});
|
||||
break;
|
||||
}
|
||||
case Config.homeassistant.id_sensor_roomtemp:
|
||||
updates.push({
|
||||
component: "desk",
|
||||
data: ha_entity.state,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
const service_result = await hpService.updatePartial(updates);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
|
||||
fastify.post(
|
||||
"/homepage/update/tidal",
|
||||
{
|
||||
preHandler: verifyAPIKey,
|
||||
schema: {
|
||||
description: "Update information for component on dpu status page",
|
||||
tags: ["homepage"],
|
||||
body: z.custom<TidalGetCurrent>(),
|
||||
response: {
|
||||
200: z.string(),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
hide: true,
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const update = request.body as TidalGetCurrent;
|
||||
const service_result = await hpService.updatePartial([
|
||||
{
|
||||
component: "tidal",
|
||||
data: update,
|
||||
},
|
||||
]);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type TidalGetCurrent,
|
||||
type WsService,
|
||||
} from "@dpu/shared";
|
||||
import { logInfo, logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import { logInfo } from "@dpu/shared/dist/logger.js";
|
||||
import type { GristService } from "../grist/service";
|
||||
import type { HomeAssistantService } from "../homeassistant/service";
|
||||
import type { TidalService } from "../tidal/service";
|
||||
@@ -58,10 +58,8 @@ export class HomepageService extends BaseService<null> {
|
||||
grist_personal_goals: personal_goals,
|
||||
}),
|
||||
);
|
||||
} catch {
|
||||
const error_message = "error getting all information";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting all information.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,7 +73,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
|
||||
private async _getTemp(): Promise<string | null> {
|
||||
const temp = await this.haService.getTemperatureText();
|
||||
const temp = await this.haService.getTemperature();
|
||||
return temp.successful ? temp.result : null;
|
||||
}
|
||||
|
||||
@@ -110,22 +108,37 @@ export class HomepageService extends BaseService<null> {
|
||||
return newPoll;
|
||||
}
|
||||
|
||||
async updatePartial(components: string[]): Promise<void> {
|
||||
async updatePartial(
|
||||
components: ComponentUpdate[],
|
||||
): Promise<ServiceResult<string>> {
|
||||
const updates: ComponentUpdate[] = [];
|
||||
for (const component of components) {
|
||||
switch (component) {
|
||||
switch (component.component) {
|
||||
case "desk": {
|
||||
this.updateHaDesk(await this._getDesk(), updates);
|
||||
this.updateHaDesk(
|
||||
(component.data as API_HA_DeskPosition) ?? (await this._getDesk()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "temp":
|
||||
this.updateHaTemp(await this._getTemp(), updates);
|
||||
this.updateHaTemp(
|
||||
(component.data as string) ?? (await this._getTemp()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
case "tidal":
|
||||
this.updateTidal(await this._getTidal(), updates);
|
||||
this.updateTidal(
|
||||
(component.data as TidalGetCurrent) ?? (await this._getTidal()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
case "grist":
|
||||
this.updateGristPG(await this._getGristPG(), updates);
|
||||
this.updateGristPG(
|
||||
(component.data as GristRecord_PersonalGoals) ??
|
||||
(await this._getGristPG()),
|
||||
updates,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
@@ -135,9 +148,11 @@ export class HomepageService extends BaseService<null> {
|
||||
type: "update",
|
||||
data: updates,
|
||||
});
|
||||
|
||||
return this.getSuccessfulResult("update broadcasted");
|
||||
}
|
||||
|
||||
private updateHaDesk(
|
||||
updateHaDesk(
|
||||
new_ha_desk_position: API_HA_DeskPosition | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -153,10 +168,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateHaTemp(
|
||||
new_ha_temp: string | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
updateHaTemp(new_ha_temp: string | null, updates: ComponentUpdate[]): void {
|
||||
if (this.lastPoll.ha_temp !== new_ha_temp) {
|
||||
this.lastPoll.ha_temp = new_ha_temp;
|
||||
updates.push({
|
||||
@@ -166,7 +178,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateTidal(
|
||||
updateTidal(
|
||||
new_tidal_current: TidalGetCurrent | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -184,7 +196,7 @@ export class HomepageService extends BaseService<null> {
|
||||
}
|
||||
}
|
||||
|
||||
private updateGristPG(
|
||||
updateGristPG(
|
||||
new_grist_personal_goals: GristRecord_PersonalGoals | null,
|
||||
updates: ComponentUpdate[],
|
||||
): void {
|
||||
@@ -227,10 +239,16 @@ export class HomepageService extends BaseService<null> {
|
||||
startPolling(): void {
|
||||
logInfo("Polling started");
|
||||
|
||||
const config: [string[], number][] = [
|
||||
[["tidal"], 20_000],
|
||||
[["desk"], 60_000],
|
||||
[["temp", "grist"], 600_000],
|
||||
const config: [ComponentUpdate[], number][] = [
|
||||
//[[{ component: "tidal", data: null }], 20_000],
|
||||
//[[{ component: "desk", data: null }], 60_000],
|
||||
[
|
||||
[
|
||||
//{ component: "temp", data: null },
|
||||
{ component: "grist", data: null },
|
||||
],
|
||||
600_000,
|
||||
],
|
||||
];
|
||||
|
||||
this.pollingIntervals = config.map(([components, interval]) =>
|
||||
|
||||
@@ -129,7 +129,7 @@ await fastify.register(gristRoutes, { gristService });
|
||||
await fastify.register(homeAssistantRoutes, { haService, verifyAPIKey });
|
||||
await fastify.register(tidalRoutes, { tidalService, verifyAPIKey });
|
||||
await fastify.register(wsRoutes, { wsService });
|
||||
await fastify.register(homepageRoutes, { hpService });
|
||||
await fastify.register(homepageRoutes, { hpService, haService, verifyAPIKey });
|
||||
|
||||
fastify.get(
|
||||
"/ping",
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
type ServiceResult,
|
||||
type TidalGetCurrent,
|
||||
} from "@dpu/shared";
|
||||
import { logWarning } from "@dpu/shared/dist/logger.js";
|
||||
import type { TidalClient } from "./client.js";
|
||||
|
||||
export class TidalService extends BaseService<TidalClient> {
|
||||
@@ -27,10 +26,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
const response = await this.getClient().get<TidalGetCurrent>("current");
|
||||
|
||||
return this.getSuccessfulResult(response);
|
||||
} catch {
|
||||
const error_message = "error getting song from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting song from tidal.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,10 +38,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
return this.getSuccessfulResult(
|
||||
Math.round(response.volume * 100), // * 100 because it's a decimal and we want a percentage
|
||||
);
|
||||
} catch {
|
||||
const error_message = "error getting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error getting volume from tidal.", error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +68,7 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
return await this.setVolumeToTidal(clampValue);
|
||||
}
|
||||
|
||||
const error_message = "error parsing volume to set";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
return this.getErrorResult("error parsing volume to set.");
|
||||
}
|
||||
|
||||
async setVolumeToTidal(
|
||||
@@ -88,10 +81,8 @@ export class TidalService extends BaseService<TidalClient> {
|
||||
);
|
||||
|
||||
return this.getSuccessfulResult(Math.round(volume));
|
||||
} catch {
|
||||
const error_message = "error setting volume from tidal";
|
||||
logWarning(error_message);
|
||||
return this.getErrorResult(error_message);
|
||||
} catch (error) {
|
||||
return this.getErrorResult("error setting volume from tidal.", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user