[Kotlin, Голосовые интерфейсы, Разработка мобильных приложений, Разработка под Android] Как встроить голосового помощника в любое мобильное приложение. Разбираем на примере Habitica
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Вам не кажется, что многие мобильные приложения стали бы куда удобнее, будь в них голосовое управление? Нет, речь не о том, чтобы вести беседы с банковским ассистентом в чате техподдержки. В основном было бы достаточно голосовой навигации по приложению или form-filling в режиме диалога.
На примере Habitica (опенсорсный app для закрепления привычек и достижения целей, написан на Kotlin) Виталя Горбачёв, архитектор решений в Just AI, показывает, как быстро и бесшовно встроить голосовой интерфейс в функционал любого приложения.
Но для начала давайте обсудим, почему голосовое управление мобильным приложением — это удобно? Начнем с очевидных вещей.
- Нам часто нужно воспользоваться приложением в момент, когда заняты руки: готовка, управление транспортным средством, тащим чемоданы, во время механической работы и так далее.
- Голос — важнейший инструмент для людей с нарушениями зрения.
Кейсы и так прозрачные, но на самом деле всё еще проще: в некоторых случаях набор голосом просто быстрее! Представьте — заказ авиабилета одной фразой «Купи мне билет на завтра на двоих в Самару» вместо долгого заполнения формы. При этом с возможностью задавать пользователю уточняющие вопросы: вечером или днем? с багажом или без?
Голос полезен при прохождении нами сценария «form-filling» и удобен для заполнения почти любых длинных форм, требующих от пользователя определенного объема информации. И такие формы присутствуют в большинстве мобильных приложений.
Слева направо: приложение РЖД «Пригород», дневник питания FatSecret (пользователям приходится заполнять форму несколько раз в день, выбирая из сотен продуктов), приложение пекарни «Коржов».
Из-за того, что сегодня голосовых ассистентов часто внедряют в чат поддержки и развиваются они именно оттуда, большинство компаний пытается запихнуть функционал приложения в чат. Пополнить баланс, узнать что-то о товаре или услуге… Это далеко не всегда удобно реализовано, а в случае с голосовым вводом и вовсе контрпродуктивно, хотя бы потому, что рапознавание речи часто работает совсем не идеально.
Правильный подход — встраивать ассистента бесшовно в уже существующий функционал приложения, в интерфейсе которого будет происходить заполнение формы, чтобы человек мог просто проверить, что он все правильно сказал, и нажать ОК.
Мы решили показать, как это можно сделать, на примере Habitica — это опенсорсное приложение, написаное почти на чистом Котлине. «Хабитика» отлично подходит под кейс с голосовым ассистентом — тут тоже для того, чтобы завести новую задачу, требуется заполнить довольно объемную форму. Попробуем заменить этот муторный процесс одной фразой с наводящими вопросами?
Я разбил туториал на две части. В этой статье мы разберемся, как добавить голосового ассистента в мобильное приложение и реализовать базовый сценарий (в нашем случае это готовый сценарий по уточнению прогноза погоды и времени — один из самых популярных в мире запросов к голосовым ассистентам). Во второй статье — а она выйдет уже скоро — мы научимся вызывать голосом определенные экраны и реализовывать сложные запросы внутри приложения.
Что нужно для работы
SDK. Мы взяли Aimybox как SDK для построения диалоговых интерфейсов. Из коробки Aimybox дает SDK ассистента и лаконичный и кастомизируемый UI(который при желании можно и вовсе переделать). При этом в качестве движков распознавания, синтеза и NLP можно выбрать из уже имеющихся или создать свой модуль.
По сути, Aimybox реализует архитектуру голосового помощника, стандартизируя интерфейсы всех этих модулей и правильным образом организуя их взаимодействие. Таким образом, внедряя это решение, можно значительно сократить время на разработку голосового интерфейса внутри своего приложения. Подробнее про Aimybox можно прочитать тут или тут.
Инструмент для создания сценария. Сценарий будем писать на JAICF (это опенсорсный и совершенно бесплатный фреймворк для разработки голосовых приложений от Just AI), а интенты распознавать с помощью Caila (NLU-сервис) в JAICP (Just AI Conversational Platform). Про них подробнее расскажу в следующей части туториала — когда дойдем до их использования.
Смартфон. Для тестов нам понадобится смартфон на Android, на котором мы будем запускать и тестить «Хабитику».
Порядок действий
Для начала форкаем «Хабитику» (ветку Release) и ищем самые важные для нас файлы. Я пользовался IDE Android Studio:
Находим MainActivity.kt — туда мы будем встраивать логику.
HabiticaBaseApplication.kt — там будем инициализировать Aimybox.
Activity_main.xml — туда встроим элемент интерфейса.
AndroidManifest.xml — там хранится вся структура приложения и его разрешения.
Согласно инструкции в репе «Хабитики» переименовываем habitica.properties.example и habitica.resources.example, убирая из них example, заводим проект в firebase под приложение и копируем в корень файл google-services.json.
Запускаем приложение, чтобы проверить, что сборка рабочая. Вуаля!
Для начала добавим зависимости Aimybox.
implementation 'com.justai.aimybox:core:0.11.0'
implementation("com.justai.aimybox:components:0.1.8")
в dependencies и
maven { url 'https://dl.bintray.com/aimybox/aimybox-android-sdk/' }
maven { url "https://dl.bintray.com/aimybox/aimybox-android-assistant/" }
в repositories.
И добавим сразу после compileOptions следующую строку, чтобы все работало корректно
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
}
Теперь разрешения.
Убираем флаги с разрешений RECORD_AUDIO и MODIFY_AUDIO_SETTINGS в AndroidManifest.xml, чтобы опции выглядели следующим образом.
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="com.android.vending.BILLING" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
Теперь инициализурем Aimybox в BaseApplication.
Добавляем AimyboxProvider при инициализации класса.
И делаем собственно инициализацию.
private fun createAimybox (context: Context): Aimybox {
val unitId = UUID.randomUUID().toString()
val textToSpeech = GooglePlatformTextToSpeech(context, Locale("Ru"))
val speechToText = GooglePlatformSpeechToText(context, Locale("Ru"))
val dialogApi = AimyboxDialogApi(
"YOUR KEY", unitId)
return Aimybox(Config.create(speechToText, textToSpeech, dialogApi))
}
Вместо YOUR_KEY впоследствии будет ваш код от Aimybox Console.
Теперь встраиваем фрагмент в mainActivity.kt. Предварительно вставляем ФреймЛэйаут в activity_main.xml, прямо под фреймлэйаутом с id bottom_navigation
<FrameLayout
android:id="@+id/assistant_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
В сам MainActivity сначала добавляем эксплицитный запрос разрешений в OnCreate
ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.RECORD_AUDIO), 1)
И при их получении добавляем фрагмент в указанный выше фрейм.
@SuppressLint("MissingPermission")
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(R.id.assistant_container, AimyboxAssistantFragment())
fragmentTransaction.commit()
}
Не забываем добавить в OnBackPressed возможности выйти из ассистента после захода в него.
val assistantFragment = (supportFragmentManager.findFragmentById(R.id.assistant_container)
as? AimyboxAssistantFragment)
if (assistantFragment?.onBackPressed() != true) {
return
}
Кроме этого, добавим в стили (styles.xml) в AppTheme
<item name="aimybox_assistantButtonTheme">@style/CustomAssistantButtonTheme</item>
<item name="aimybox_recognitionTheme">@style/CustomRecognitionWidgetTheme</item>
<item name="aimybox_responseTheme">@style/CustomResponseWidgetTheme</item>
<item name="aimybox_imageReplyTheme">@style/CustomImageReplyWidgetTheme</item>
<item name="aimybox_buttonReplyTheme">@style/CustomButtonReplyWidgetTheme</item>
И отдельные стили чуть ниже:
<style name="CustomAssistantButtonTheme" parent="DefaultAssistantTheme.AssistantButton">
</style>
<style name="CustomRecognitionWidgetTheme" parent="DefaultAssistantTheme.Widget.Recognition">
</style>
<style name="CustomResponseWidgetTheme" parent="DefaultAssistantTheme.Widget.Response">
</style>
<style name="CustomButtonReplyWidgetTheme" parent="DefaultAssistantTheme.Widget.ButtonReply">
</style>
<style name="CustomImageReplyWidgetTheme" parent="DefaultAssistantTheme.Widget.ImageReply">
</style>
Давайте проверим, добавился ли микрофончик. Запускаем приложение.
У нас посыпалась куча ошибок о неправильном синтаксисе. Исправляем все, как советует IDE.
Работает!
Но микрофончик наползает на нижнюю навигацию. Давайте чуть поднимем. Добавим в стили выше в CustomAssistantButtonTheme:
<item name="aimybox_buttonMarginBottom">72dp</item>
Лучше!
Теперь подключим туда асисстента и проверим, нормально ли он отвечает. Для этого нам понадобится консоль Aimybox.
Начнем с того, что зайдем в app.aimybox.com под нашим акком гитхаба, сделаем новый проект, подключим пару навыков (я подключил DateTime для теста) и попробуем задать соответсвующие вопросы в асисстенте. Здесь же в настройках, в правом верхнем углу, берем apiKey, который вставляем в createAimybox вместо YOUR KEY.
private fun createAimybox (context: Context): Aimybox {
val unitId = UUID.randomUUID().toString()
val textToSpeech = GooglePlatformTextToSpeech(context)
val speechToText = GooglePlatformSpeechToText(context)
val dialogApi = AimyboxDialogApi(
"YOUR KEY", unitId)
return Aimybox(Config.create(speechToText, textToSpeech, dialogApi))
}
Работает!
Только надпись на английском, давайте поменяем приветственное сообщение в strings.constants.xml.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Prefs -->
<string name="SP_userID" translatable="false">UserID</string>
<string name="SP_APIToken" translatable="false">APIToken</string>
<string name="base_url" translatable="false">https://habitica.com</string>
<string name="initial_phrase">"Привет! Чем могу помочь?</string>
Ура!
Вот ссылка на репозиторий с кодом.
В следующей статье про ассистента для «Хабитики» расскажу, как с помощью голоса не только узнавать погоду, а управлять непосредственно приложением — переходить по страничкам и добавлять привычки и задания.
===========
Источник:
habr.com
===========
Похожие новости:
- [Processing, Платежные системы, Разработка мобильных приложений] Как работают мобильные кошельки на примере приложения «Mir Pay»
- [Open source] Обзор OpenStack Neutron PTG июнь 2020
- [Data Mining, Natural Language Processing, Python, Изучение языков, Машинное обучение] Делаем параллельный корпус из книг с помощью sentence embeddings
- [Google App Engine, Разработка мобильных приложений, Разработка под Android] Google начала публичный альфа-тест Jetpack Compose
- [Разработка мобильных приложений, Разработка под Android, Социальные сети и сообщества] Приложения для сети Fediverse могут удалить из Google Play из-за недопустимого контента
- [Open source, *nix] FOSS News №31 – дайджест новостей свободного и открытого ПО за 24-30 августа 2020 года
- [Разработка под iOS, Разработка мобильных приложений, Алгоритмы] Что мы знаем о секретной пасхалке гугла для разработчиков?
- [Open source, Программирование, Системное программирование, Компиляторы, Rust] Rust 1.46.0: track_caller и улучшения const fn (перевод)
- [Тестирование IT-систем, Разработка мобильных приложений, Разработка для интернета вещей, Бизнес-модели] Марк маркировал, маркировал, да и вымаркировал. Маркировка — это ж просто!?
- [JavaScript, Node.JS, Open source, Разработка веб-сайтов] Сладкая жизнь, или Создание веб-приложения без написания кода
Теги для поиска: #_kotlin, #_golosovye_interfejsy (Голосовые интерфейсы), #_razrabotka_mobilnyh_prilozhenij (Разработка мобильных приложений), #_razrabotka_pod_android (Разработка под Android), #_android, #_kotlin, #_jaicf, #_aimybox, #_jaicp, #_caila, #_android_development, #_nlp, #_nlp_(natural_language_processing), #_nlu, #_mobile_development, #_open_source, #_mobile_apps, #_voice, #_blog_kompanii_just_ai (
Блог компании Just AI
), #_kotlin, #_golosovye_interfejsy (
Голосовые интерфейсы
), #_razrabotka_mobilnyh_prilozhenij (
Разработка мобильных приложений
), #_razrabotka_pod_android (
Разработка под Android
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:20
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Вам не кажется, что многие мобильные приложения стали бы куда удобнее, будь в них голосовое управление? Нет, речь не о том, чтобы вести беседы с банковским ассистентом в чате техподдержки. В основном было бы достаточно голосовой навигации по приложению или form-filling в режиме диалога. На примере Habitica (опенсорсный app для закрепления привычек и достижения целей, написан на Kotlin) Виталя Горбачёв, архитектор решений в Just AI, показывает, как быстро и бесшовно встроить голосовой интерфейс в функционал любого приложения. Но для начала давайте обсудим, почему голосовое управление мобильным приложением — это удобно? Начнем с очевидных вещей.
Кейсы и так прозрачные, но на самом деле всё еще проще: в некоторых случаях набор голосом просто быстрее! Представьте — заказ авиабилета одной фразой «Купи мне билет на завтра на двоих в Самару» вместо долгого заполнения формы. При этом с возможностью задавать пользователю уточняющие вопросы: вечером или днем? с багажом или без? Голос полезен при прохождении нами сценария «form-filling» и удобен для заполнения почти любых длинных форм, требующих от пользователя определенного объема информации. И такие формы присутствуют в большинстве мобильных приложений.
Слева направо: приложение РЖД «Пригород», дневник питания FatSecret (пользователям приходится заполнять форму несколько раз в день, выбирая из сотен продуктов), приложение пекарни «Коржов». Из-за того, что сегодня голосовых ассистентов часто внедряют в чат поддержки и развиваются они именно оттуда, большинство компаний пытается запихнуть функционал приложения в чат. Пополнить баланс, узнать что-то о товаре или услуге… Это далеко не всегда удобно реализовано, а в случае с голосовым вводом и вовсе контрпродуктивно, хотя бы потому, что рапознавание речи часто работает совсем не идеально. Правильный подход — встраивать ассистента бесшовно в уже существующий функционал приложения, в интерфейсе которого будет происходить заполнение формы, чтобы человек мог просто проверить, что он все правильно сказал, и нажать ОК.
Я разбил туториал на две части. В этой статье мы разберемся, как добавить голосового ассистента в мобильное приложение и реализовать базовый сценарий (в нашем случае это готовый сценарий по уточнению прогноза погоды и времени — один из самых популярных в мире запросов к голосовым ассистентам). Во второй статье — а она выйдет уже скоро — мы научимся вызывать голосом определенные экраны и реализовывать сложные запросы внутри приложения. Что нужно для работы SDK. Мы взяли Aimybox как SDK для построения диалоговых интерфейсов. Из коробки Aimybox дает SDK ассистента и лаконичный и кастомизируемый UI(который при желании можно и вовсе переделать). При этом в качестве движков распознавания, синтеза и NLP можно выбрать из уже имеющихся или создать свой модуль. По сути, Aimybox реализует архитектуру голосового помощника, стандартизируя интерфейсы всех этих модулей и правильным образом организуя их взаимодействие. Таким образом, внедряя это решение, можно значительно сократить время на разработку голосового интерфейса внутри своего приложения. Подробнее про Aimybox можно прочитать тут или тут. Инструмент для создания сценария. Сценарий будем писать на JAICF (это опенсорсный и совершенно бесплатный фреймворк для разработки голосовых приложений от Just AI), а интенты распознавать с помощью Caila (NLU-сервис) в JAICP (Just AI Conversational Platform). Про них подробнее расскажу в следующей части туториала — когда дойдем до их использования. Смартфон. Для тестов нам понадобится смартфон на Android, на котором мы будем запускать и тестить «Хабитику». Порядок действий Для начала форкаем «Хабитику» (ветку Release) и ищем самые важные для нас файлы. Я пользовался IDE Android Studio: Находим MainActivity.kt — туда мы будем встраивать логику. HabiticaBaseApplication.kt — там будем инициализировать Aimybox. Activity_main.xml — туда встроим элемент интерфейса. AndroidManifest.xml — там хранится вся структура приложения и его разрешения. Согласно инструкции в репе «Хабитики» переименовываем habitica.properties.example и habitica.resources.example, убирая из них example, заводим проект в firebase под приложение и копируем в корень файл google-services.json. Запускаем приложение, чтобы проверить, что сборка рабочая. Вуаля! Для начала добавим зависимости Aimybox. implementation 'com.justai.aimybox:core:0.11.0'
implementation("com.justai.aimybox:components:0.1.8") в dependencies и maven { url 'https://dl.bintray.com/aimybox/aimybox-android-sdk/' }
maven { url "https://dl.bintray.com/aimybox/aimybox-android-assistant/" } в repositories. И добавим сразу после compileOptions следующую строку, чтобы все работало корректно kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString() } Теперь разрешения. Убираем флаги с разрешений RECORD_AUDIO и MODIFY_AUDIO_SETTINGS в AndroidManifest.xml, чтобы опции выглядели следующим образом. <uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="com.android.vending.BILLING" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/> Теперь инициализурем Aimybox в BaseApplication. Добавляем AimyboxProvider при инициализации класса. И делаем собственно инициализацию. private fun createAimybox (context: Context): Aimybox {
val unitId = UUID.randomUUID().toString() val textToSpeech = GooglePlatformTextToSpeech(context, Locale("Ru")) val speechToText = GooglePlatformSpeechToText(context, Locale("Ru")) val dialogApi = AimyboxDialogApi( "YOUR KEY", unitId) return Aimybox(Config.create(speechToText, textToSpeech, dialogApi)) } Вместо YOUR_KEY впоследствии будет ваш код от Aimybox Console. Теперь встраиваем фрагмент в mainActivity.kt. Предварительно вставляем ФреймЛэйаут в activity_main.xml, прямо под фреймлэйаутом с id bottom_navigation <FrameLayout
android:id="@+id/assistant_container" android:layout_width="match_parent" android:layout_height="match_parent"/> В сам MainActivity сначала добавляем эксплицитный запрос разрешений в OnCreate ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.RECORD_AUDIO), 1)
И при их получении добавляем фрагмент в указанный выше фрейм. @SuppressLint("MissingPermission")
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { val fragmentManager = supportFragmentManager val fragmentTransaction = fragmentManager.beginTransaction() fragmentTransaction.add(R.id.assistant_container, AimyboxAssistantFragment()) fragmentTransaction.commit() } Не забываем добавить в OnBackPressed возможности выйти из ассистента после захода в него. val assistantFragment = (supportFragmentManager.findFragmentById(R.id.assistant_container)
as? AimyboxAssistantFragment) if (assistantFragment?.onBackPressed() != true) { return } Кроме этого, добавим в стили (styles.xml) в AppTheme <item name="aimybox_assistantButtonTheme">@style/CustomAssistantButtonTheme</item>
<item name="aimybox_recognitionTheme">@style/CustomRecognitionWidgetTheme</item> <item name="aimybox_responseTheme">@style/CustomResponseWidgetTheme</item> <item name="aimybox_imageReplyTheme">@style/CustomImageReplyWidgetTheme</item> <item name="aimybox_buttonReplyTheme">@style/CustomButtonReplyWidgetTheme</item> И отдельные стили чуть ниже: <style name="CustomAssistantButtonTheme" parent="DefaultAssistantTheme.AssistantButton">
</style> <style name="CustomRecognitionWidgetTheme" parent="DefaultAssistantTheme.Widget.Recognition"> </style> <style name="CustomResponseWidgetTheme" parent="DefaultAssistantTheme.Widget.Response"> </style> <style name="CustomButtonReplyWidgetTheme" parent="DefaultAssistantTheme.Widget.ButtonReply"> </style> <style name="CustomImageReplyWidgetTheme" parent="DefaultAssistantTheme.Widget.ImageReply"> </style> Давайте проверим, добавился ли микрофончик. Запускаем приложение. У нас посыпалась куча ошибок о неправильном синтаксисе. Исправляем все, как советует IDE. Работает! Но микрофончик наползает на нижнюю навигацию. Давайте чуть поднимем. Добавим в стили выше в CustomAssistantButtonTheme: <item name="aimybox_buttonMarginBottom">72dp</item>
Лучше! Теперь подключим туда асисстента и проверим, нормально ли он отвечает. Для этого нам понадобится консоль Aimybox. Начнем с того, что зайдем в app.aimybox.com под нашим акком гитхаба, сделаем новый проект, подключим пару навыков (я подключил DateTime для теста) и попробуем задать соответсвующие вопросы в асисстенте. Здесь же в настройках, в правом верхнем углу, берем apiKey, который вставляем в createAimybox вместо YOUR KEY. private fun createAimybox (context: Context): Aimybox {
val unitId = UUID.randomUUID().toString() val textToSpeech = GooglePlatformTextToSpeech(context) val speechToText = GooglePlatformSpeechToText(context) val dialogApi = AimyboxDialogApi( "YOUR KEY", unitId) return Aimybox(Config.create(speechToText, textToSpeech, dialogApi)) } Работает! Только надпись на английском, давайте поменяем приветственное сообщение в strings.constants.xml. <?xml version="1.0" encoding="utf-8"?>
<resources> <!-- Prefs --> <string name="SP_userID" translatable="false">UserID</string> <string name="SP_APIToken" translatable="false">APIToken</string> <string name="base_url" translatable="false">https://habitica.com</string> <string name="initial_phrase">"Привет! Чем могу помочь?</string> Ура! Вот ссылка на репозиторий с кодом. В следующей статье про ассистента для «Хабитики» расскажу, как с помощью голоса не только узнавать погоду, а управлять непосредственно приложением — переходить по страничкам и добавлять привычки и задания. =========== Источник: habr.com =========== Похожие новости:
Блог компании Just AI ), #_kotlin, #_golosovye_interfejsy ( Голосовые интерфейсы ), #_razrabotka_mobilnyh_prilozhenij ( Разработка мобильных приложений ), #_razrabotka_pod_android ( Разработка под Android ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 23:20
Часовой пояс: UTC + 5