summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/home/telegram/bot.py18
-rwxr-xr-xsrc/sound_bot.py536
2 files changed, 237 insertions, 317 deletions
diff --git a/src/home/telegram/bot.py b/src/home/telegram/bot.py
index 9f1e8f3..004f795 100644
--- a/src/home/telegram/bot.py
+++ b/src/home/telegram/bot.py
@@ -25,7 +25,7 @@ from home.api.types import BotType
from ._botlang import lang, languages
from ._botdb import BotDatabase
-from ._botutil import ReportingHelper, exc2text, IgnoreMarkup
+from ._botutil import ReportingHelper, exc2text, IgnoreMarkup, user_any_name
from ._botcontext import Context
@@ -112,7 +112,7 @@ def handler(**kwargs):
elif 'command' in kwargs:
_updater.dispatcher.add_handler(CommandHandler(kwargs['command'], _handler), group=0)
elif 'callback' in kwargs:
- _updater.dispatcher.add_handler(CallbackQueryHandler(_handler), group=0)
+ _updater.dispatcher.add_handler(CallbackQueryHandler(_handler, pattern=kwargs['callback']), group=0)
return _handler
return inner
@@ -124,12 +124,14 @@ def simplehandler(f: callable):
return _handler
-def callbackhandler(f: callable):
- @wraps(f)
- def _handler(*args, **kwargs):
- return _handler_of_handler(f=f, *args, **kwargs)
- _updater.dispatcher.add_handler(CallbackQueryHandler(_handler), group=0)
- return _handler
+def callbackhandler(**kwargs):
+ def inner(f):
+ @wraps(f)
+ def _handler(*args, **kwargs):
+ return _handler_of_handler(f=f, *args, **kwargs)
+ _updater.dispatcher.add_handler(CallbackQueryHandler(_handler, pattern=kwargs['callback']), group=0)
+ return _handler
+ return inner
def exceptionhandler(f: callable):
diff --git a/src/sound_bot.py b/src/sound_bot.py
index 91e51f0..59d065a 100755
--- a/src/sound_bot.py
+++ b/src/sound_bot.py
@@ -9,7 +9,6 @@ from html import escape
from typing import Optional, List, Dict, Tuple
from home.config import config
-from home.bot import Wrapper, Context, text_filter, user_any_name
from home.api import WebAPIClient
from home.api.types import SoundSensorLocation, BotType
from home.api.errors import ApiResponseError
@@ -17,19 +16,126 @@ from home.media import SoundNodeClient, SoundRecordClient, SoundRecordFile, Came
from home.soundsensor import SoundSensorServerGuardClient
from home.util import parse_addr, chunks, filesize_fmt
+from home.telegram import bot
+
from telegram.error import TelegramError
from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton, User
-from telegram.ext import (
- CallbackQueryHandler,
- MessageHandler
-)
from PIL import Image
+config.load('sound_bot')
+
+nodes = {}
+for nodename, nodecfg in config['nodes'].items():
+ nodes[nodename] = parse_addr(nodecfg['addr'])
+
+bot.initialize()
+bot.lang.ru(
+ start_message="Выберите команду на клавиатуре",
+ unknown_command="Неизвестная команда",
+ unexpected_callback_data="Ошибка: неверные данные",
+ settings="Настройки микшера",
+ record="Запись",
+ loading="Загрузка...",
+ select_place="Выберите место:",
+ invalid_location="Неверное место",
+ invalid_interval="Неверная длительность",
+ unsupported_action="Неподдерживаемое действие",
+ # select_control="Выберите контрол для изменения настроек:",
+ control_state="Состояние контрола %s",
+ incr="громкость +",
+ decr="громкость -",
+ back="◀️ Назад",
+ n_min="%d мин.",
+ n_sec="%d сек.",
+ select_interval="Выберите длительность:",
+ place="Место",
+ beginning="Начало",
+ end="Конец",
+ record_result="Результат записи",
+ record_started='Запись запущена!',
+ record_error="Ошибка записи",
+ files="Локальные файлы",
+ remote_files="Файлы на сервере",
+ file_line="— Запись с <b>%s</b> до <b>%s</b> <i>(%s)</i>",
+ access_denied="Доступ запрещён",
+
+ guard_disable="Снять с охраны",
+ guard_enable="Поставить на охрану",
+ guard_status="Статус охраны",
+ guard_user_action_notification='Пользователь <a href="tg://user?id=%d">%s</a> %s.',
+ guard_user_action_enable="включил охрану ✅",
+ guard_user_action_disable="выключил охрану ❌",
+ guard_status_enabled="Включена ✅",
+ guard_status_disabled="Выключена ❌",
+
+ done="Готово 👌",
+
+ sound_sensors="Датчики звука",
+ sound_sensors_info="Здесь можно получить информацию о последних срабатываниях датчиков звука.",
+ sound_sensors_no_24h_data="За последние 24 часа данных нет.",
+ sound_sensors_show_anything="Показать, что есть",
+
+ cameras="Камеры",
+ select_option="Выберите опцию",
+ w_flash="Со вспышкой",
+ wo_flash="Без вспышки",
+)
+
+bot.lang.en(
+ start_message="Select command on the keyboard",
+ unknown_command="Unknown command",
+ settings="Mixer settings",
+ record="Record",
+ unexpected_callback_data="Unexpected callback data",
+ loading="Loading...",
+ select_place="Select place:",
+ invalid_location="Invalid place",
+ invalid_interval="Invalid duration",
+ unsupported_action="Unsupported action",
+ # select_control="Select control to adjust its parameters:",
+ control_state="%s control state",
+ incr="vol +",
+ decr="vol -",
+ back="◀️ Back",
+ n_min="%d min.",
+ n_sec="%d s.",
+ select_interval="Select duration:",
+ place="Place",
+ beginning="Started",
+ end="Ended",
+ record_result="Result",
+ record_started='Recording started!',
+ record_error="Recording error",
+ files="Local files",
+ remote_files="Remote files",
+ file_line="— From <b>%s</b> to <b>%s</b> <i>(%s)</i>",
+ access_denied="Access denied",
+
+ guard_disable="Disable guard",
+ guard_enable="Enable guard",
+ guard_status="Guard status",
+ guard_user_action_notification='User <a href="tg://user?id=%d">%s</a> %s.',
+ guard_user_action_enable="turned the guard ON ✅",
+ guard_user_action_disable="turn the guard OFF ❌",
+ guard_status_enabled="Active ✅",
+ guard_status_disabled="Disabled ❌",
+ done="Done 👌",
+
+ sound_sensors="Sound sensors",
+ sound_sensors_info="Here you can get information about last sound sensors hits.",
+ sound_sensors_no_24h_data="No data for the last 24 hours.",
+ sound_sensors_show_anything="Show me at least something",
+
+ cameras="Cameras",
+ select_option="Select option",
+ w_flash="With flash",
+ wo_flash="Without flash",
+)
+
logger = logging.getLogger(__name__)
RenderedContent = Tuple[str, Optional[InlineKeyboardMarkup]]
record_client: Optional[SoundRecordClient] = None
-bot: Optional[Wrapper] = None
node_client_links: Dict[str, SoundNodeClient] = {}
cam_client_links: Dict[str, CameraNodeClient] = {}
@@ -73,7 +179,7 @@ def interval_defined(interval: int) -> bool:
return interval in config['bot']['record_intervals']
-def callback_unpack(ctx: Context) -> List[str]:
+def callback_unpack(ctx: bot.Context) -> List[str]:
return ctx.callback_query.data[3:].split('/')
@@ -90,7 +196,7 @@ def guard_client() -> SoundSensorServerGuardClient:
class Renderer:
@classmethod
- def places_markup(cls, ctx: Context, callback_prefix: str) -> InlineKeyboardMarkup:
+ def places_markup(cls, ctx: bot.Context, callback_prefix: str) -> InlineKeyboardMarkup:
buttons = []
for node, nodeconfig in config['nodes'].items():
buttons.append([InlineKeyboardButton(nodeconfig['label'][ctx.user_lang], callback_data=f'{callback_prefix}/{node}')])
@@ -98,7 +204,7 @@ class Renderer:
@classmethod
def back_button(cls,
- ctx: Context,
+ ctx: bot.Context,
buttons: list,
callback_data: str):
buttons.append([
@@ -108,13 +214,13 @@ class Renderer:
class SettingsRenderer(Renderer):
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'<b>{ctx.lang("settings")}</b>\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='s0')
@classmethod
- def node(cls, ctx: Context,
+ def node(cls, ctx: bot.Context,
controls: List[dict]) -> RenderedContent:
node, = callback_unpack(ctx)
@@ -132,7 +238,7 @@ class SettingsRenderer(Renderer):
return html, InlineKeyboardMarkup(buttons)
@classmethod
- def control(cls, ctx: Context, data) -> RenderedContent:
+ def control(cls, ctx: bot.Context, data) -> RenderedContent:
node, control, *rest = callback_unpack(ctx)
html = '<b>' + ctx.lang('control_state', control) + '</b>\n\n'
@@ -163,13 +269,13 @@ class SettingsRenderer(Renderer):
class RecordRenderer(Renderer):
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'<b>{ctx.lang("record")}</b>\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='r0')
@classmethod
- def node(cls, ctx: Context, durations: List[int]) -> RenderedContent:
+ def node(cls, ctx: bot.Context, durations: List[int]) -> RenderedContent:
node, = callback_unpack(ctx)
html = ctx.lang('select_interval')
@@ -188,7 +294,7 @@ class RecordRenderer(Renderer):
return html, InlineKeyboardMarkup(buttons)
@classmethod
- def record_started(cls, ctx: Context, rid: int) -> RenderedContent:
+ def record_started(cls, ctx: bot.Context, rid: int) -> RenderedContent:
node, *rest = callback_unpack(ctx)
place = config['nodes'][node]['label'][ctx.user_lang]
@@ -235,13 +341,13 @@ class RecordRenderer(Renderer):
class FilesRenderer(Renderer):
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'<b>{ctx.lang("files")}</b>\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='f0')
@classmethod
- def filelist(cls, ctx: Context, files: List[SoundRecordFile]) -> RenderedContent:
+ def filelist(cls, ctx: bot.Context, files: List[SoundRecordFile]) -> RenderedContent:
node, = callback_unpack(ctx)
html_files = map(lambda file: cls.file(ctx, file, node), files)
@@ -253,7 +359,7 @@ class FilesRenderer(Renderer):
return html, InlineKeyboardMarkup(buttons)
@classmethod
- def file(cls, ctx: Context, file: SoundRecordFile, node: str) -> str:
+ def file(cls, ctx: bot.Context, file: SoundRecordFile, node: str) -> str:
html = ctx.lang('file_line', file.start_humantime, file.stop_humantime, filesize_fmt(file.filesize))
if file.file_id is not None:
html += f'/audio_{node}_{file.file_id}'
@@ -262,7 +368,7 @@ class FilesRenderer(Renderer):
class RemoteFilesRenderer(FilesRenderer):
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'<b>{ctx.lang("remote_files")}</b>\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='g0')
@@ -270,7 +376,7 @@ class RemoteFilesRenderer(FilesRenderer):
class SoundSensorRenderer(Renderer):
@classmethod
- def places_markup(cls, ctx: Context, callback_prefix: str) -> InlineKeyboardMarkup:
+ def places_markup(cls, ctx: bot.Context, callback_prefix: str) -> InlineKeyboardMarkup:
buttons = []
for sensor, sensor_label in config['sound_sensors'].items():
buttons.append(
@@ -278,13 +384,13 @@ class SoundSensorRenderer(Renderer):
return InlineKeyboardMarkup(buttons)
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'{ctx.lang("sound_sensors_info")}\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='S0')
@classmethod
- def hits(cls, ctx: Context, data, is_last=False) -> RenderedContent:
+ def hits(cls, ctx: bot.Context, data, is_last=False) -> RenderedContent:
node, = callback_unpack(ctx)
buttons = []
@@ -307,7 +413,7 @@ class SoundSensorRenderer(Renderer):
return html, InlineKeyboardMarkup(buttons)
@classmethod
- def hits_plain(cls, ctx: Context, data, is_last=False) -> bytes:
+ def hits_plain(cls, ctx: bot.Context, data, is_last=False) -> bytes:
node, = callback_unpack(ctx)
text = ''
@@ -326,13 +432,13 @@ class SoundSensorRenderer(Renderer):
class CamerasRenderer(Renderer):
@classmethod
- def index(cls, ctx: Context) -> RenderedContent:
+ def index(cls, ctx: bot.Context) -> RenderedContent:
html = f'<b>{ctx.lang("cameras")}</b>\n\n'
html += ctx.lang('select_place')
return html, cls.places_markup(ctx, callback_prefix='c0')
@classmethod
- def places_markup(cls, ctx: Context, callback_prefix: str) -> InlineKeyboardMarkup:
+ def places_markup(cls, ctx: bot.Context, callback_prefix: str) -> InlineKeyboardMarkup:
buttons = []
for camera_name, camera_data in config['cameras'].items():
buttons.append(
@@ -340,7 +446,7 @@ class CamerasRenderer(Renderer):
return InlineKeyboardMarkup(buttons)
@classmethod
- def camera(cls, ctx: Context, flash_available: bool) -> RenderedContent:
+ def camera(cls, ctx: bot.Context, flash_available: bool) -> RenderedContent:
node, = callback_unpack(ctx)
html = ctx.lang('select_option')
@@ -355,7 +461,7 @@ class CamerasRenderer(Renderer):
return html, InlineKeyboardMarkup([buttons])
#
# @classmethod
- # def record_started(cls, ctx: Context, rid: int) -> RenderedContent:
+ # def record_started(cls, ctx: bot.Context, rid: int) -> RenderedContent:
# node, *rest = callback_unpack(ctx)
#
# place = config['nodes'][node]['label'][ctx.user_lang]
@@ -403,7 +509,10 @@ class CamerasRenderer(Renderer):
# cameras handlers
# ----------------
-def cameras(ctx: Context):
+@bot.handler(message='cameras', callback=r'^c0$')
+def cameras(ctx: bot.Context):
+ """ List of cameras """
+
text, markup = CamerasRenderer.index(ctx)
if not ctx.is_callback_context():
return ctx.reply(text, markup=markup)
@@ -412,7 +521,10 @@ def cameras(ctx: Context):
return ctx.edit(text, markup=markup)
-def camera_options(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^c0/.*')
+def camera_options(ctx: bot.Context) -> None:
+ """ List of options (with/without flash etc) """
+
cam, = callback_unpack(ctx)
if not camera_exists(cam):
ctx.answer(ctx.lang('invalid_location'))
@@ -425,7 +537,10 @@ def camera_options(ctx: Context) -> None:
ctx.edit(text, markup)
-def camera_capture(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^c1/.*')
+def camera_capture(ctx: bot.Context) -> None:
+ """ Cheese """
+
cam, flash = callback_unpack(ctx)
flash = int(flash)
if not camera_exists(cam):
@@ -464,7 +579,10 @@ def camera_capture(ctx: Context) -> None:
# settings handlers
# -----------------
-def settings(ctx: Context):
+@bot.handler(message='settings', callback=r'^s0$')
+def settings(ctx: bot.Context):
+ """ List of nodes """
+
text, markup = SettingsRenderer.index(ctx)
if not ctx.is_callback_context():
return ctx.reply(text, markup=markup)
@@ -473,7 +591,10 @@ def settings(ctx: Context):
return ctx.edit(text, markup=markup)
-def settings_place(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^s0/.*')
+def settings_place(ctx: bot.Context):
+ """ List of controls """
+
node, = callback_unpack(ctx)
if not node_exists(node):
ctx.answer(ctx.lang('invalid_location'))
@@ -488,7 +609,10 @@ def settings_place(ctx: Context) -> None:
ctx.edit(text, markup)
-def settings_place_control(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^s1/.*')
+def settings_place_control(ctx: bot.Context):
+ """ List of available tunes for control """
+
node, control = callback_unpack(ctx)
if not node_exists(node):
ctx.answer(ctx.lang('invalid_location'))
@@ -503,7 +627,10 @@ def settings_place_control(ctx: Context) -> None:
ctx.edit(text, markup)
-def settings_place_control_action(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^s2/.*')
+def settings_place_control_action(ctx: bot.Context):
+ """ Tuning """
+
node, control, action = callback_unpack(ctx)
if not node_exists(node):
return
@@ -525,7 +652,10 @@ def settings_place_control_action(ctx: Context) -> None:
# recording handlers
# ------------------
-def record(ctx: Context):
+@bot.handler(message='record', callback=r'^r0$')
+def record(ctx: bot.Context):
+ """ List of nodes """
+
if not manual_recording_allowed(ctx.user_id):
return ctx.reply(ctx.lang('access_denied'))
@@ -537,7 +667,10 @@ def record(ctx: Context):
return ctx.edit(text, markup=markup)
-def record_place(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^r0/.*')
+def record_place(ctx: bot.Context):
+ """ List of available intervals """
+
node, = callback_unpack(ctx)
if not node_exists(node):
ctx.answer(ctx.lang('invalid_location'))
@@ -549,7 +682,10 @@ def record_place(ctx: Context) -> None:
ctx.edit(text, markup)
-def record_place_interval(ctx: Context) -> None:
+@bot.callbackhandler(callback=r'^r1/.*')
+def record_place_interval(ctx: bot.Context):
+ """ Do record! """
+
node, interval = callback_unpack(ctx)
interval = int(interval)
if not node_exists(node):
@@ -572,38 +708,13 @@ def record_place_interval(ctx: Context) -> None:
ctx.edit(html, markup)
-# files handlers
-# --------------
-
-# def files(ctx: Context, remote=False):
-# renderer = RemoteFilesRenderer if remote else FilesRenderer
-# text, markup = renderer.index(ctx)
-# if not ctx.is_callback_context():
-# return ctx.reply(text, markup=markup)
-# else:
-# ctx.answer()
-# return ctx.edit(text, markup=markup)
-#
-#
-# def files_list(ctx: Context):
-# node, = callback_unpack(ctx)
-# if not node_exists(node):
-# ctx.answer(ctx.lang('invalid_location'))
-# return
-#
-# ctx.answer()
-#
-# cl = node_client(node)
-# files = cl.storage_list(extended=True, as_objects=True)
-#
-# text, markup = FilesRenderer.filelist(ctx, files)
-# ctx.edit(text, markup)
-
-
# sound sensor handlers
# ---------------------
-def sound_sensors(ctx: Context):
+@bot.handler(message='sound_sensors', callback=r'^S0$')
+def sound_sensors(ctx: bot.Context):
+ """ List of places """
+
text, markup = SoundSensorRenderer.index(ctx)
if not ctx.is_callback_context():
return ctx.reply(text, markup=markup)
@@ -612,7 +723,10 @@ def sound_sensors(ctx: Context):
return ctx.edit(text, markup=markup)
-def sound_sensors_last_24h(ctx: Context):
+@bot.callbackhandler(callback=r'^S0/.*')
+def sound_sensors_last_24h(ctx: bot.Context):
+ """ Last 24h log """
+
node, = callback_unpack(ctx)
if not sound_sensor_exists(node):
ctx.answer(ctx.lang('invalid location'))
@@ -632,7 +746,10 @@ def sound_sensors_last_24h(ctx: Context):
ctx.edit(text, markup=markup)
-def sound_sensors_last_anything(ctx: Context):
+@bot.callbackhandler(callback=r'^S1/.*')
+def sound_sensors_last_anything(ctx: bot.Context):
+ """ Last _something_ """
+
node, = callback_unpack(ctx)
if not sound_sensor_exists(node):
ctx.answer(ctx.lang('invalid location'))
@@ -660,38 +777,58 @@ class GuardUserAction(Enum):
DISABLE = 'disable'
-def guard_status(ctx: Context):
- guard = guard_client()
- resp = guard.guard_status()
+if 'guard_server' in config['bot']:
+ @bot.handler(message='guard_status')
+ def guard_status(ctx: bot.Context):
+ guard = guard_client()
+ resp = guard.guard_status()
- key = 'enabled' if resp['enabled'] is True else 'disabled'
- ctx.reply(ctx.lang(f'guard_status_{key}'))
+ key = 'enabled' if resp['enabled'] is True else 'disabled'
+ ctx.reply(ctx.lang(f'guard_status_{key}'))
-def guard_enable(ctx: Context):
- guard = guard_client()
- guard.guard_enable()
- ctx.reply(ctx.lang('done'))
+ @bot.handler(message='guard_enable')
+ def guard_enable(ctx: bot.Context):
+ guard = guard_client()
+ guard.guard_enable()
+ ctx.reply(ctx.lang('done'))
- _guard_notify(ctx.user, GuardUserAction.ENABLE)
+ _guard_notify(ctx.user, GuardUserAction.ENABLE)
-def guard_disable(ctx: Context):
- guard = guard_client()
- guard.guard_disable()
- ctx.reply(ctx.lang('done'))
+ @bot.handler(message='guard_disable')
+ def guard_disable(ctx: bot.Context):
+ guard = guard_client()
+ guard.guard_disable()
+ ctx.reply(ctx.lang('done'))
- _guard_notify(ctx.user, GuardUserAction.DISABLE)
+ _guard_notify(ctx.user, GuardUserAction.DISABLE)
-def _guard_notify(user: User, action: GuardUserAction):
- def text_getter(lang: str):
- action_name = bot.lang.get(f'guard_user_action_{action.value}', lang)
- user_name = user_any_name(user)
- return 'ℹ ' + bot.lang.get('guard_user_action_notification', lang,
- user.id, user_name, action_name)
+ def _guard_notify(user: User, action: GuardUserAction):
+ def text_getter(lang: str):
+ action_name = bot.lang.get(f'guard_user_action_{action.value}', lang)
+ user_name = bot.user_any_name(user)
+ return 'ℹ ' + bot.lang.get('guard_user_action_notification', lang,
+ user.id, user_name, action_name)
- bot.notify_all(text_getter, exclude=(user.id,))
+ bot.notify_all(text_getter, exclude=(user.id,))
+
+
+@bot.defaultreplymarkup
+def markup(ctx: Optional[bot.Context]) -> Optional[ReplyKeyboardMarkup]:
+ buttons = [
+ [ctx.lang('record'), ctx.lang('settings')],
+ # [ctx.lang('files'), ctx.lang('remote_files')],
+ ]
+ if 'guard_server' in config['bot']:
+ buttons.append([
+ ctx.lang('guard_enable'), ctx.lang('guard_disable'), ctx.lang('guard_status')
+ ])
+ buttons.append([ctx.lang('sound_sensors')])
+ if have_cameras():
+ buttons.append([ctx.lang('cameras')])
+ return ReplyKeyboardMarkup(buttons, one_time_keyboard=False)
# record client callbacks
@@ -740,232 +877,13 @@ def record_onfinished(info: dict, fn: str, userdata: dict):
logger.exception(e)
-class SoundBot(Wrapper):
- def __init__(self):
- super().__init__()
-
- self.lang.ru(
- start_message="Выберите команду на клавиатуре",
- unknown_command="Неизвестная команда",
- unexpected_callback_data="Ошибка: неверные данные",
- settings="Настройки микшера",
- record="Запись",
- loading="Загрузка...",
- select_place="Выберите место:",
- invalid_location="Неверное место",
- invalid_interval="Неверная длительность",
- unsupported_action="Неподдерживаемое действие",
- # select_control="Выберите контрол для изменения настроек:",
- control_state="Состояние контрола %s",
- incr="громкость +",
- decr="громкость -",
- back="◀️ Назад",
- n_min="%d мин.",
- n_sec="%d сек.",
- select_interval="Выберите длительность:",
- place="Место",
- beginning="Начало",
- end="Конец",
- record_result="Результат записи",
- record_started='Запись запущена!',
- record_error="Ошибка записи",
- files="Локальные файлы",
- remote_files="Файлы на сервере",
- file_line="— Запись с <b>%s</b> до <b>%s</b> <i>(%s)</i>",
- access_denied="Доступ запрещён",
-
- guard_disable="Снять с охраны",
- guard_enable="Поставить на охрану",
- guard_status="Статус охраны",
- guard_user_action_notification='Пользователь <a href="tg://user?id=%d">%s</a> %s.',
- guard_user_action_enable="включил охрану ✅",
- guard_user_action_disable="выключил охрану ❌",
- guard_status_enabled="Включена ✅",
- guard_status_disabled="Выключена ❌",
-
- done="Готово 👌",
-
- sound_sensors="Датчики звука",
- sound_sensors_info="Здесь можно получить информацию о последних срабатываниях датчиков звука.",
- sound_sensors_no_24h_data="За последние 24 часа данных нет.",
- sound_sensors_show_anything="Показать, что есть",
-
- cameras="Камеры",
- select_option="Выберите опцию",
- w_flash="Со вспышкой",
- wo_flash="Без вспышки",
- )
-
- self.lang.en(
- start_message="Select command on the keyboard",
- unknown_command="Unknown command",
- settings="Mixer settings",
- record="Record",
- unexpected_callback_data="Unexpected callback data",
- loading="Loading...",
- select_place="Select place:",
- invalid_location="Invalid place",
- invalid_interval="Invalid duration",
- unsupported_action="Unsupported action",
- # select_control="Select control to adjust its parameters:",
- control_state="%s control state",
- incr="vol +",
- decr="vol -",
- back="◀️ Back",
- n_min="%d min.",
- n_sec="%d s.",
- select_interval="Select duration:",
- place="Place",
- beginning="Started",
- end="Ended",
- record_result="Result",
- record_started='Recording started!',
- record_error="Recording error",
- files="Local files",
- remote_files="Remote files",
- file_line="— From <b>%s</b> to <b>%s</b> <i>(%s)</i>",
- access_denied="Access denied",
-
- guard_disable="Disable guard",
- guard_enable="Enable guard",
- guard_status="Guard status",
- guard_user_action_notification='User <a href="tg://user?id=%d">%s</a> %s.',
- guard_user_action_enable="turned the guard ON ✅",
- guard_user_action_disable="turn the guard OFF ❌",
- guard_status_enabled="Active ✅",
- guard_status_disabled="Disabled ❌",
- done="Done 👌",
-
- sound_sensors="Sound sensors",
- sound_sensors_info="Here you can get information about last sound sensors hits.",
- sound_sensors_no_24h_data="No data for the last 24 hours.",
- sound_sensors_show_anything="Show me at least something",
-
- cameras="Cameras",
- select_option="Select option",
- w_flash="With flash",
- wo_flash="Without flash",
- )
-
- # ------
- # settings
- # -------------
-
- # list of nodes
- self.add_handler(MessageHandler(text_filter(self.lang.all('settings')), self.wrap(settings)))
- self.add_handler(CallbackQueryHandler(self.wrap(settings), pattern=r'^s0$'))
-
- # list of controls
- self.add_handler(CallbackQueryHandler(self.wrap(settings_place), pattern=r'^s0/.*'))
-
- # list of available tunes for control
- self.add_handler(CallbackQueryHandler(self.wrap(settings_place_control), pattern=r'^s1/.*'))
-
- # tuning
- self.add_handler(CallbackQueryHandler(self.wrap(settings_place_control_action), pattern=r'^s2/.*'))
-
- # ------
- # recording
- # --------------
-
- # list of nodes
- self.add_handler(MessageHandler(text_filter(self.lang.all('record')), self.wrap(record)))
- self.add_handler(CallbackQueryHandler(self.wrap(record), pattern=r'^r0$'))
-
- # list of available intervals
- self.add_handler(CallbackQueryHandler(self.wrap(record_place), pattern=r'^r0/.*'))
-
- # do record!
- self.add_handler(CallbackQueryHandler(self.wrap(record_place_interval), pattern=r'^r1/.*'))
-
- # ---------
- # sound sensors
- # ------------------
-
- # list of places
- self.add_handler(MessageHandler(text_filter(self.lang.all('sound_sensors')), self.wrap(sound_sensors)))
- self.add_handler(CallbackQueryHandler(self.wrap(sound_sensors), pattern=r'^S0$'))
-
- # last 24h log
- self.add_handler(CallbackQueryHandler(self.wrap(sound_sensors_last_24h), pattern=r'^S0/.*'))
-
- # last _something_
- self.add_handler(CallbackQueryHandler(self.wrap(sound_sensors_last_anything), pattern=r'^S1/.*'))
-
- # -------------
- # guard enable/disable
- # -------------------------
- if 'guard_server' in config['bot']:
- self.add_handler(MessageHandler(text_filter(self.lang.all('guard_enable')), self.wrap(guard_enable)))
- self.add_handler(MessageHandler(text_filter(self.lang.all('guard_disable')), self.wrap(guard_disable)))
- self.add_handler(MessageHandler(text_filter(self.lang.all('guard_status')), self.wrap(guard_status)))
-
- # --------
- # local files
- # ----------------
-
- # list of nodes
- # self.add_handler(MessageHandler(text_filter(self.lang.all('files')), self.wrap(partial(files, remote=False))))
- # self.add_handler(CallbackQueryHandler(self.wrap(partial(files, remote=False)), pattern=r'^f0$'))
-
- # list of specific node's files
- # self.add_handler(CallbackQueryHandler(self.wrap(files_list), pattern=r'^f0/.*'))
-
- # --------
- # remote files
- # -----------------
-
- # list of nodes
- # self.add_handler(MessageHandler(text_filter(self.lang.all('remote_files')), self.wrap(partial(files, remote=True))))
- # self.add_handler(CallbackQueryHandler(self.wrap(partial(files, remote=True)), pattern=r'^g0$'))
-
- # list of specific node's files
- # self.add_handler(CallbackQueryHandler(self.wrap(files_list), pattern=r'^g0/.*'))
-
- # ------
- # cameras
- # ------------
-
- # list of cameras
- self.add_handler(MessageHandler(text_filter(self.lang.all('cameras')), self.wrap(cameras)))
- self.add_handler(CallbackQueryHandler(self.wrap(cameras), pattern=r'^c0$'))
-
- # list of options (with/without flash etc)
- self.add_handler(CallbackQueryHandler(self.wrap(camera_options), pattern=r'^c0/.*'))
-
- # cheese
- self.add_handler(CallbackQueryHandler(self.wrap(camera_capture), pattern=r'^c1/.*'))
-
- def markup(self, ctx: Optional[Context]) -> Optional[ReplyKeyboardMarkup]:
- buttons = [
- [ctx.lang('record'), ctx.lang('settings')],
- # [ctx.lang('files'), ctx.lang('remote_files')],
- ]
- if 'guard_server' in config['bot']:
- buttons.append([
- ctx.lang('guard_enable'), ctx.lang('guard_disable'), ctx.lang('guard_status')
- ])
- buttons.append([ctx.lang('sound_sensors')])
- if have_cameras():
- buttons.append([ctx.lang('cameras')])
- return ReplyKeyboardMarkup(buttons, one_time_keyboard=False)
-
-
if __name__ == '__main__':
- config.load('sound_bot')
-
- nodes = {}
- for nodename, nodecfg in config['nodes'].items():
- nodes[nodename] = parse_addr(nodecfg['addr'])
-
record_client = SoundRecordClient(nodes,
error_handler=record_onerror,
finished_handler=record_onfinished,
download_on_finish=True)
- bot = SoundBot()
if 'api' in config:
bot.enable_logging(BotType.SOUND)
bot.run()
-
record_client.stop()