Skip to content

Commit

Permalink
device buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew (from workstation) committed Jan 1, 2021
1 parent d19c2e3 commit a1c5119
Show file tree
Hide file tree
Showing 11 changed files with 189 additions and 24 deletions.
3 changes: 2 additions & 1 deletion smart_tv_telegram/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .config import Config
from .mtproto import Mtproto
from .http import Http
from .http import Http, OnStreamClosed
from .bot import Bot


Expand All @@ -13,6 +13,7 @@
"Config",
"Mtproto",
"Http",
"OnStreamClosed",
"Bot",
"__version__",
"__version_info__",
Expand Down
1 change: 1 addition & 0 deletions smart_tv_telegram/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async def async_main(config: Config):
mtproto = Mtproto(config)
http = Http(mtproto, config, finders)
bot = Bot(mtproto, config, http, finders)
http.set_on_stram_closed_handler(bot.get_on_stream_closed())
bot.prepare()

await mtproto.start()
Expand Down
91 changes: 84 additions & 7 deletions smart_tv_telegram/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
import async_timeout
from pyrogram import Client, filters
from pyrogram.filters import create
from pyrogram.handlers import MessageHandler
from pyrogram.types import ReplyKeyboardRemove, Message, KeyboardButton, ReplyKeyboardMarkup
from pyrogram.handlers import MessageHandler, CallbackQueryHandler
from pyrogram.types import ReplyKeyboardRemove, Message, KeyboardButton, ReplyKeyboardMarkup, CallbackQuery, \
InlineKeyboardMarkup, InlineKeyboardButton

from . import Config, Mtproto, Http
from .devices import Device, DeviceFinder
from . import Config, Mtproto, Http, OnStreamClosed
from .devices import Device, DeviceFinder, DevicePlayerFunction
from .tools import build_uri, pyrogram_filename, secret_token


__all__ = [
"Bot"
]
Expand Down Expand Up @@ -50,6 +50,21 @@ def __init__(self, msg_id: int, filename: str, devices: typing.List[Device]):
self.devices = devices


class OnStreamClosedHandler(OnStreamClosed):
_mtproto: Mtproto
_functions: typing.Dict[int, typing.Any]

def __init__(self, mtproto: Mtproto, functions: typing.Dict[int, typing.Any]):
self._mtproto = mtproto
self._functions = functions

async def handle(self, ramains: float, chat_id: int, message_id: int, local_token: int):
if local_token in self._functions:
del self._functions[local_token]

await self._mtproto.reply_message(message_id, chat_id, f"download closed, {ramains:0.2f}% remains")


class TelegramStateMachine:
_states: typing.Dict[int, typing.Tuple[States, typing.Union[bool, StateData]]]

Expand Down Expand Up @@ -78,13 +93,18 @@ class Bot:
_mtproto: Mtproto
_http: Http
_finders: typing.List[DeviceFinder]
_functions: typing.Dict[int, typing.Dict[int, DevicePlayerFunction]]

def __init__(self, mtproto: Mtproto, config: Config, http: Http, finders: typing.List[DeviceFinder]):
self._config = config
self._mtproto = mtproto
self._http = http
self._finders = finders
self._state_machine = TelegramStateMachine()
self._functions = dict()

def get_on_stream_closed(self) -> OnStreamClosed:
return OnStreamClosedHandler(self._mtproto, self._functions)

def prepare(self):
admin_filter = filters.chat(self._config.admins) & filters.private
Expand All @@ -95,9 +115,43 @@ def prepare(self):
self._mtproto.register(MessageHandler(self._new_document, filters.voice & admin_filter))
self._mtproto.register(MessageHandler(self._new_document, filters.video_note & admin_filter))

admin_filter_inline = create(lambda _, __, m: m.from_user.id in self._config.admins)
self._mtproto.register(CallbackQueryHandler(self._device_player_function, admin_filter_inline))

state_filter = create(lambda _, __, m: self._state_machine.get_state(m)[0] == States.SELECT)
self._mtproto.register(MessageHandler(self._select_device, filters.text & admin_filter & state_filter))

async def _device_player_function(self, _: Client, message: CallbackQuery):
data = message.data

try:
data = int(data)
except ValueError:
await message.answer("wrong callback")

try:
f = next(
f_v
for f in self._functions.values()
for f_k, f_v in f.items()
if f_k == data
)
except StopIteration:
await message.answer("stream closed")
return

if not await f.is_enabled(self._config):
await message.answer("function not enabled")
return

with async_timeout.timeout(self._config.device_request_timeout) as cm:
await f.handle(self._mtproto)

if cm.expired:
await message.answer("request timeout")
else:
await message.answer("done")

async def _select_device(self, _: Client, message: Message):
data: SelectStateData
_, data = self._state_machine.get_state(message)
Expand All @@ -121,7 +175,7 @@ async def _select_device(self, _: Client, message: Message):

async with async_timeout.timeout(self._config.device_request_timeout) as timeout_context:
token = secret_token()
self._http.add_remote_token(data.msg_id, token)
local_token = self._http.add_remote_token(data.msg_id, token)
uri = build_uri(self._config, data.msg_id, token)

# noinspection PyBroadException
Expand All @@ -138,7 +192,30 @@ async def _select_device(self, _: Client, message: Message):
)

else:
await reply(f"Playing ID: {data.msg_id}")
physical_functions = device.get_player_functions()
functions = self._functions[local_token] = dict()

if physical_functions:
buttons = []

for function in physical_functions:
function_id = secret_token()
function_name = await function.get_name()
button = InlineKeyboardButton(function_name, str(function_id))
functions[function_id] = function
buttons.append([button])

await message.reply(
f"Device <code>{html.escape(device.get_device_name())}</code>\n"
f"controller for file <code>{data.msg_id}</code>",
reply_markup=InlineKeyboardMarkup(buttons)
)

stub_message = await reply("stub")
await stub_message.delete()

else:
await reply(f"Playing file <code>{data.msg_id}</code>")

if timeout_context.expired:
await reply("Timeout while communicate with the device")
Expand Down
5 changes: 3 additions & 2 deletions smart_tv_telegram/devices/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import typing

from .device import Device, DeviceFinder, RoutersDefType, RequestHandler
from .device import Device, DeviceFinder, RoutersDefType, RequestHandler, DevicePlayerFunction
from .upnp_device import UpnpDevice, UpnpDeviceFinder
from .chromecast_device import ChromecastDevice, ChromecastDeviceFinder
from .vlc_device import VlcDeviceFinder, VlcDevice
Expand Down Expand Up @@ -32,5 +32,6 @@
"RoutersDefType",
"RequestHandler",
"WebDeviceFinder",
"WebDevice"
"WebDevice",
"DevicePlayerFunction"
]
5 changes: 4 additions & 1 deletion smart_tv_telegram/devices/chromecast_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pychromecast

from . import Device, DeviceFinder, RoutersDefType
from . import Device, DeviceFinder, RoutersDefType, DevicePlayerFunction
from .. import Config
from ..tools import run_method_in_executor

Expand Down Expand Up @@ -31,6 +31,9 @@ def play(self, url: str, title: str):
self._device.media_controller.play_media(url, "video/mp4", title=title)
self._device.media_controller.block_until_active()

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return []


class ChromecastDeviceFinder(DeviceFinder):
@run_method_in_executor
Expand Down
21 changes: 20 additions & 1 deletion smart_tv_telegram/devices/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from aiohttp.web_request import Request
from aiohttp.web_response import Response

from .. import Config
from .. import Config, Mtproto


class RequestHandler(abc.ABC):
Expand All @@ -25,9 +25,24 @@ async def handle(self, request: Request) -> Response:
"DeviceFinder",
"RoutersDefType",
"RequestHandler",
"DevicePlayerFunction"
]


class DevicePlayerFunction(abc.ABC):
@abc.abstractmethod
async def get_name(self) -> str:
raise NotImplementedError

@abc.abstractmethod
async def handle(self, mtproto: Mtproto):
raise NotImplementedError

@abc.abstractmethod
async def is_enabled(self, config: Config):
raise NotImplementedError


class Device(abc.ABC):
@abc.abstractmethod
async def stop(self):
Expand All @@ -41,6 +56,10 @@ async def play(self, url: str, title: str):
def get_device_name(self) -> str:
raise NotImplementedError

@abc.abstractmethod
def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
raise NotImplementedError

def __repr__(self):
return self.get_device_name()

Expand Down
44 changes: 42 additions & 2 deletions smart_tv_telegram/devices/upnp_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
from async_upnp_client.aiohttp import AiohttpRequester
from async_upnp_client.search import async_search

from . import Device, DeviceFinder, RoutersDefType
from .. import Config
from . import Device, DeviceFinder, RoutersDefType, DevicePlayerFunction
from .. import Config, Mtproto
from ..tools import ascii_only


Expand Down Expand Up @@ -37,6 +37,40 @@
"""


class UpnpPlayFunction(DevicePlayerFunction):
_service: async_upnp_client.UpnpService

def __init__(self, service: async_upnp_client.UpnpService):
self._service = service

async def get_name(self) -> str:
return "PLAY"

async def handle(self, mtproto: Mtproto):
play = self._service.action("Play")
await play.async_call(InstanceID=0, Speed="1")

async def is_enabled(self, config: Config):
return config.upnp_enabled


class UpnpPauseFunction(DevicePlayerFunction):
_service: async_upnp_client.UpnpService

def __init__(self, service: async_upnp_client.UpnpService):
self._service = service

async def get_name(self) -> str:
return "PAUSE"

async def handle(self, mtproto: Mtproto):
play = self._service.action("Pause")
await play.async_call(InstanceID=0)

async def is_enabled(self, config: Config):
return config.upnp_enabled


class UpnpDevice(Device):
_device: async_upnp_client.UpnpDevice
_service: async_upnp_client.UpnpService
Expand Down Expand Up @@ -65,6 +99,12 @@ async def play(self, url: str, title: str):
play = self._service.action("Play")
await play.async_call(InstanceID=0, Speed="1")

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return [
UpnpPlayFunction(self._service),
UpnpPauseFunction(self._service)
]


class UpnpDeviceFinder(DeviceFinder):
async def find(self, config: Config) -> typing.List[Device]:
Expand Down
5 changes: 4 additions & 1 deletion smart_tv_telegram/devices/vlc_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import logging
import typing

from . import DeviceFinder, Device, RoutersDefType
from . import DeviceFinder, Device, RoutersDefType, DevicePlayerFunction
from .. import Config


Expand Down Expand Up @@ -88,6 +88,9 @@ async def play(self, url: str, title: str):
await self._call("add", url)
await self._call("play")

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return []


class VlcDeviceFinder(DeviceFinder):
async def find(self, config: Config) -> typing.List[Device]:
Expand Down
8 changes: 7 additions & 1 deletion smart_tv_telegram/devices/web_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from aiohttp.web_response import Response

from smart_tv_telegram import Config
from smart_tv_telegram.devices import DeviceFinder, RoutersDefType, Device, RequestHandler
from smart_tv_telegram.devices import DeviceFinder, RoutersDefType, Device, RequestHandler, DevicePlayerFunction
from smart_tv_telegram.tools import secret_token, AsyncDebounce


Expand Down Expand Up @@ -41,6 +41,9 @@ def get_url_to_play(self) -> typing.Optional[str]:
self._url_to_play = None
return tmp

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return []


class WebDeviceApiRequestRegisterDevice(RequestHandler):
_config: Config
Expand Down Expand Up @@ -109,6 +112,9 @@ async def handle(self, request: Request) -> Response:

return Response(status=200, body=url_to_play)

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return []


class WebDeviceFinder(DeviceFinder):
_devices: typing.Dict[WebDevice, AsyncDebounce]
Expand Down
5 changes: 4 additions & 1 deletion smart_tv_telegram/devices/xbmc_device.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import aiohttp

from . import Device, DeviceFinder, RoutersDefType
from . import Device, DeviceFinder, RoutersDefType, DevicePlayerFunction
from .. import Config


Expand Down Expand Up @@ -136,6 +136,9 @@ async def play(self, url: str, title: str):
await self._call("Playlist.Add", playlistid=0, item={"file": url})
await self._call("Player.Open", item={"playlistid": 0}, options={"repeat": "one"})

def get_player_functions(self) -> typing.List[DevicePlayerFunction]:
return []


class XbmcDeviceFinder(DeviceFinder):
async def find(self, config: Config) -> typing.List[Device]:
Expand Down
Loading

0 comments on commit a1c5119

Please sign in to comment.