Skip to content
This repository has been archived by the owner on Jun 25, 2024. It is now read-only.

Commit

Permalink
Ported to pyatv, compatible with tvOS 15
Browse files Browse the repository at this point in the history
  • Loading branch information
stigger committed Sep 26, 2021
1 parent 4bdcb06 commit b611e3e
Show file tree
Hide file tree
Showing 54 changed files with 76 additions and 6,344 deletions.
3 changes: 0 additions & 3 deletions .gitmodules

This file was deleted.

3 changes: 1 addition & 2 deletions data/config.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
device_info:
name: Trakt Scrobbler
{}
103 changes: 33 additions & 70 deletions media_remote.py
Original file line number Diff line number Diff line change
@@ -1,87 +1,50 @@
import asyncio
import sys
import os
import uuid
from typing import cast

import varint
from pairing import introduce, pairing, setup_keys, verify
from protobuf_gen import ProtocolMessage_pb2
import pyatv
import pyatv.core.relayer


class MediaRemoteProtocol(asyncio.Protocol):
class MediaRemoteProtocol(pyatv.interface.DeviceListener):
def __init__(self, config) -> None:
super().__init__()
self.config = config
self.pending_data = bytes()
self.output_key = None
self.output_nonce = 0
self.input_key = None
self.input_nonce = 0
self.transport = None
self.atv = None
self.protocol = None

@staticmethod
def do_encryption_operation(operation, data, nonce):
res = operation(b'\0\0\0\0' + nonce.to_bytes(8, sys.byteorder), data, None)
nonce += 1
if nonce == 0xFFFFFFFF:
nonce = 0
return res, nonce

def encrypt(self, data):
res, self.output_nonce = self.do_encryption_operation(self.output_key.encrypt, data, self.output_nonce)
return res

def decrypt(self, data):
res, self.input_nonce = self.do_encryption_operation(self.input_key.decrypt, data, self.input_nonce)
return res

def connection_made(self, transport):
self.transport = transport
socket = transport.get_extra_info('socket')

socket.setblocking(True)
transport.pause_reading()

introduce(socket, self.config['device_info'])
async def connect(self, atv):
loop = asyncio.get_event_loop()

if not os.path.exists('data/pairing.state'):
pairing(socket, self.config['device_info'])

self.output_key, self.input_key = setup_keys(verify(socket, self.config['device_info']))
self.output_nonce = 0
self.input_nonce = 0
pairing = await pyatv.pair(atv, pyatv.Protocol.AirPlay, loop)
await pairing.begin()

code = input("Enter code displayed by Apple TV: ")
pairing.pin(code)

await pairing.finish()
if pairing.has_paired:
with open("data/pairing.state", "w") as f:
f.write(pairing.service.credentials)
else:
print("Could not pair", file=sys.stderr)
exit(1)
else:
with open("data/pairing.state", "r") as f:
atv.set_credentials(pyatv.Protocol.AirPlay, f.read())

self.atv = await pyatv.connect(atv, loop)
self.atv.listener = self
self.protocol = cast(pyatv.protocols.mrp_proto.MrpProtocol,
cast(pyatv.core.relayer.Relayer, self.atv.remote_control).main_instance.protocol)

socket.setblocking(False)
transport.resume_reading()

msg = ProtocolMessage_pb2.ProtocolMessage()
msg.type = ProtocolMessage_pb2.ProtocolMessage.SET_READY_STATE_MESSAGE
self.send(msg)
print("ready!")

def data_received(self, data):
self.pending_data += data

while len(self.pending_data) > 0:
length = varint.decode_bytes(self.pending_data)
length_bytes = len(varint.encode(length))

if len(self.pending_data) < length + length_bytes:
break
data = self.pending_data[length_bytes:length_bytes + length]
self.pending_data = self.pending_data[length_bytes + length:]
def connection_lost(self, exception: Exception) -> None:
loop = asyncio.get_event_loop()
asyncio.run_coroutine_threadsafe(pyatv.connect(self.atv, loop), loop)

msg = ProtocolMessage_pb2.ProtocolMessage()
decrypted = self.decrypt(data)
msg.ParseFromString(decrypted)

self.message_received(msg)

def message_received(self, msg):
def connection_closed(self) -> None:
pass

def send(self, msg):
msg.identifier = str(uuid.uuid1())
data = self.encrypt(msg.SerializeToString())
self.transport.write(varint.encode(len(data)))
self.transport.write(data)
1 change: 0 additions & 1 deletion mediaremotetv-protocol
Submodule mediaremotetv-protocol deleted from a60c50
244 changes: 0 additions & 244 deletions pairing.py

This file was deleted.

Loading

0 comments on commit b611e3e

Please sign in to comment.