Skip to content

Commit

Permalink
Merge pull request #296 from sebbo2002/develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sebbo2002 committed Jan 30, 2024
2 parents e48326d + 6419027 commit 9e5110d
Show file tree
Hide file tree
Showing 13 changed files with 620 additions and 289 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/test-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
# https://github.com/aio-libs/aiohttp/issues/7739#issuecomment-1773868351
python-version: 3.11
- name: 🔧 Setup pip cache
uses: actions/cache@v3
uses: actions/cache@v4
id: pip-cache
with:
path: ~/.cache/pip
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
with:
python-version: 3.11
- name: 🔧 Setup pip cache
uses: actions/cache@v3
uses: actions/cache@v4
id: pip-cache
with:
path: ~/.cache/pip
Expand Down Expand Up @@ -110,7 +110,7 @@ jobs:
with:
python-version: 3.11
- name: 🔧 Setup pip cache
uses: actions/cache@v3
uses: actions/cache@v4
id: pip-cache
with:
path: ~/.cache/pip
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2023 Sebastian Pekarek
Copyright (c) 2024 Sebastian Pekarek

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
Expand Down
593 changes: 357 additions & 236 deletions package-lock.json

Large diffs are not rendered by default.

18 changes: 9 additions & 9 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@
},
"description": "A lightweight wrapper around pyatv…",
"devDependencies": {
"@qiwi/semantic-release-gh-pages-plugin": "^5.2.10",
"@qiwi/semantic-release-gh-pages-plugin": "^5.2.12",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/exec": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@semantic-release/npm": "^11.0.2",
"@types/mocha": "^10.0.6",
"@types/node": "^20.10.2",
"@types/node": "^20.11.5",
"@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "^6.13.2",
"@typescript-eslint/parser": "^6.13.2",
"c8": "^8.0.1",
"eslint": "^8.55.0",
"eslint-plugin-jsonc": "^2.11.2",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.18.1",
"c8": "^9.1.0",
"eslint": "^8.56.0",
"eslint-plugin-jsonc": "^2.13.0",
"esm": "^3.2.25",
"license-checker": "^25.0.1",
"mocha": "^10.2.0",
"mochawesome": "^7.1.3",
"semantic-release": "^22.0.12",
"semantic-release": "^23.0.0",
"semantic-release-license": "^1.0.3",
"source-map-support": "^0.5.21",
"ts-node": "^10.9.2",
"tsup": "^8.0.1",
"typedoc": "^0.25.4",
"typedoc": "^0.25.7",
"typescript": "^5.3.3"
},
"engines": {
Expand Down
34 changes: 27 additions & 7 deletions src/lib/device-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ import {ChildProcess} from 'child_process';

import {EventEmitter} from 'events';
import {NodePyATVDevice, NodePyATVDeviceEvent} from '../lib/index.js';
import {addRequestId, debug, execute, getParamters, parseState, removeRequestId} from './tools.js';
import {
addRequestId,
compareOutputDevices,
debug,
execute,
getParameters,
parseState,
removeRequestId
} from './tools.js';
import {FakeChildProcess} from './fake-spawn.js';

/**
Expand All @@ -38,19 +46,28 @@ export default class NodePyATVDeviceEvents extends EventEmitter {
applyStateAndEmitEvents(newState: NodePyATVState): void {
let keys = Object.keys(this.state);

if('power_state' in newState && newState.power_state) {
keys = ['power_state'];
if('powerState' in newState && newState.powerState) {
keys = ['powerState'];
}
if('focusState' in newState && newState.focusState) {
keys = ['focusState'];
}
if('focus_state' in newState && newState.focus_state) {
keys = ['focus_state'];
if('outputDevices' in newState && newState.outputDevices) {
keys = ['outputDevices'];
}

// Volume events don't hold the complete state…
// see https://github.com/sebbo2002/node-pyatv/pull/291
if('volume' in newState && newState.volume) {
if('volume' in newState && newState.volume !== null) {
keys = ['volume'];
}

// If all values are null, we don't need to emit events at all
// https://github.com/sebbo2002/node-pyatv/issues/295#issuecomment-1888640079
if(!Object.entries(newState).find(([k, v]) => !['result', 'dateTime'].includes(k) && v !== null)) {
return;
}

keys.forEach((key: string) => {

// @ts-ignore
Expand All @@ -62,6 +79,9 @@ export default class NodePyATVDeviceEvents extends EventEmitter {
if(oldValue === undefined || newValue === undefined || oldValue === newValue) {
return;
}
if(key === 'outputDevices' && compareOutputDevices(oldValue, newValue)) {
return;
}

const event = new NodePyATVDeviceEvent({
key: key as NodePyATVStateIndex,
Expand Down Expand Up @@ -147,7 +167,7 @@ export default class NodePyATVDeviceEvents extends EventEmitter {
this.listenerState = NodePyATVListenerState.starting;

const listenStart = new Date().getTime();
const parameters = getParamters(this.options);
const parameters = getParameters(this.options);
this.pyatv = execute(reqId, NodePyATVExecutableType.atvscript, [...parameters, 'push_updates'], this.options);
if(!this.pyatv) {
throw new Error('Unable to start listener: Unable to start atvscript');
Expand Down
8 changes: 4 additions & 4 deletions src/lib/device.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
NodePyATVState
} from './types.js';

import { addRequestId, getParamters, parseState, removeRequestId, request } from './tools.js';
import { addRequestId, getParameters, parseState, removeRequestId, request } from './tools.js';
import { NodePyATVDeviceEvent, NodePyATVDeviceEvents } from '../lib/index.js';
import { EventEmitter } from 'events';

Expand Down Expand Up @@ -229,7 +229,7 @@ export default class NodePyATVDevice implements EventEmitter{
const id = addRequestId();

try {
const parameters = getParamters(this.options);
const parameters = getParameters(this.options);

const result = await request(id, NodePyATVExecutableType.atvscript, [...parameters, 'playing'], this.options);
const newState = parseState(result, id, this.options);
Expand Down Expand Up @@ -401,7 +401,7 @@ export default class NodePyATVDevice implements EventEmitter{
*/
async listApps(): Promise<NodePyATVApp[]> {
const id = addRequestId();
const parameters = getParamters(this.options);
const parameters = getParameters(this.options);

const result = await request(id, NodePyATVExecutableType.atvremote, [...parameters, 'app_list'], this.options);
if(typeof result !== 'string' || !result.startsWith('App: ')) {
Expand All @@ -426,7 +426,7 @@ export default class NodePyATVDevice implements EventEmitter{

private async _pressKey(key: NodePyATVInternalKeys | string, executableType: NodePyATVExecutableType) {
const id = addRequestId();
const parameters = getParamters(this.options);
const parameters = getParameters(this.options);

const result = await request(id, executableType, [...parameters, key], this.options);
if (
Expand Down
4 changes: 2 additions & 2 deletions src/lib/instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
NodePyATVVersionResponse
} from './types.js';

import { addRequestId, debug, getParamters, removeRequestId, request } from './tools.js';
import { addRequestId, debug, getParameters, removeRequestId, request } from './tools.js';
import { NodePyATVDevice } from '../lib/index.js';

/**
Expand Down Expand Up @@ -109,7 +109,7 @@ export default class NodePyATVInstance {
*/
public static async find (options: NodePyATVFindAndInstanceOptions = {}): Promise<NodePyATVDevice[]> {
const id = addRequestId();
const parameters = getParamters(options);
const parameters = getParameters(options);

const result = await request(id, NodePyATVExecutableType.atvscript, [...parameters, 'scan'], options);
if (typeof result !== 'object' || result.result !== 'success' || !Array.isArray(result.devices)) {
Expand Down
21 changes: 18 additions & 3 deletions src/lib/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ export async function request(
return result.stdout;
}

export function getParamters(options: NodePyATVFindAndInstanceOptions = {}): string[] {
export function getParameters(options: NodePyATVFindAndInstanceOptions = {}): string[] {
const parameters: string[] = [];

if (options.hosts) {
Expand Down Expand Up @@ -253,7 +253,8 @@ export function parseState(input: NodePyATVInternalState, id: string, options: N
appId: null,
powerState: null,
volume: null,
focusState: null
focusState: null,
outputDevices: null
};
if (!input || typeof input !== 'object') {
return result;
Expand Down Expand Up @@ -390,7 +391,7 @@ export function parseState(input: NodePyATVInternalState, id: string, options: N
if(typeof input.focus_state === 'string') {
const validValues = Object.keys(NodePyATVFocusState).map(o => String(o));
if (validValues.includes(input.focus_state)) {
result.focusState = NodePyATVFocusState[input.power_state as NodePyATVFocusState];
result.focusState = NodePyATVFocusState[input.focus_state as NodePyATVFocusState];
}
else {
d(`Unsupported focusState value ${input.focus_state}, ignore attribute`);
Expand All @@ -399,5 +400,19 @@ export function parseState(input: NodePyATVInternalState, id: string, options: N
d(`No focusState value found in input (${JSON.stringify(input)})`);
}

// outputDevices
if (Array.isArray(input.output_devices)) {
result.outputDevices = input.output_devices;
}

return result;
}

export function compareOutputDevices (a: NodePyATVState['outputDevices'], b: NodePyATVState['outputDevices']) {
return Boolean(
Array.isArray(a) &&
Array.isArray(b) &&
JSON.stringify(a.sort((a, b) => a.identifier < b.identifier ? -1 : 1)) ===
JSON.stringify(b.sort((a, b) => a.identifier < b.identifier ? -1 : 1))
);
}
9 changes: 9 additions & 0 deletions src/lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,14 @@ export enum NodePyATVPowerState {
}

export enum NodePyATVFocusState {
// @deprecated Please use `NodePyATVFocusState.focused` instead
focued = 'focused',

// Doublicate enum value due to typo, will be removed in next breaking change
// @todo remove in next breaking change
// eslint-disable-next-line @typescript-eslint/no-duplicate-enum-values
focused = 'focused',

unfocused = 'unfocused'
}

Expand Down Expand Up @@ -214,6 +221,7 @@ export interface NodePyATVInternalState {
connection?: string | unknown;
volume?: number | unknown;
focus_state?: string | unknown;
output_devices?: Array<{ name: string; identifier: string; }>;
}

export interface NodePyATVState {
Expand All @@ -234,6 +242,7 @@ export interface NodePyATVState {
powerState: NodePyATVPowerState | null;
volume: number | null;
focusState: NodePyATVFocusState | null;
outputDevices: Array<{ name: string; identifier: string; }> | null;
}

export interface NodePyATVApp {
Expand Down
Loading

0 comments on commit 9e5110d

Please sign in to comment.