# Kotlin

# Веб приложение с Kotlin.js: Начало

Почему Kotlin.js? 

Почему стоит использовать Kotlin для веб разработки? Вот несколько причин: 
* Знакомство: Если вы пришли из компилируемого языка типа Java, Swift или C#, вы найдете Kotlin очень простым для изучения, и вы должны уже быть знакомы с ним, если вы разработчик для андроид. 
* Дружелюбные инструменты: IDE может помочь вам во многом с JavaScript, так как Kotlin идет с первоклассной поддержкой в IDE, значит вы можете находить, общие ошибки при наборе кода. 
* Распространение кода между платформами: С помощью Kotlin Multiplatform Project, вы можете написать бизнес логику вашего приложения один раз и распространять его на множество платформ включая бэкенд, браузер фротенд, Android и iOS клиенты. 
* Совместимость: Возможность вызывать JavaScript код из Kotlin кода очень важна. Она позволяет вам использовать уже готовый JavaScript код, который был уже написан на Kotlin. Эта совместимость имеет и обратное направление. Можно вызывать Kotlin код из JavaScript. 
* Проблемы JavaScript: Kotlin отличный выбор если отказались от веб разработки в связи с распространением JavaScript проблем, таких как необходимость работы с динамической типизацией, странным логическим сравнением или прототипированием. 
* Требования: для этой инструкции, вам необходимо базовое знание веб-программирования и знакомство с Kotlin и IntelliJ Idea. Если вы полностью новичок в Kotlin, возможно вы захотите почитать книгу "Kotlin Apprentice" или видео курс "Программирования на Kotlin" 

# Начнем. 

Вы собираетесь создать приложение книжный магазин. Приложение будет получать данные о книгах с вебсервиса, отображать обложки книг с названием, описанием, ценой и кнопкой открыть страничку с деталями. Вот как будет выглядеть конечный результат: 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593118305977.png) 

Чтобы следовать данному руководству, вам необходимо использовать IntelliJ IDEA 2018.8.7 или позднее (бесплатная Community Edition будет достаточно), и Kotlin 1.2.61 или более поздний. 

Начинаем с загрузки материала для этого руководства используя кнопку "Download Materials" в верхней или нижней части окна программы. Затем запускаем IntelliJ IDEA  и создаем новый проект. 

В окне нового проекта, выберите Kotlin в левой части панели и  Kotlin/JS в правой части. Затем нажмите "Next" 

Новый проект, шаг 1 

Для имени проекта используйте `bookstore`. Выберите место расположения проекта, или используйте по умолчанию. Затем нажмите "Finish" 

Новый проект, шаг 2 

Распакуйте скаченные материалы, и из начальной папки, скопируйте **index.html**  и **styles.css** в вашу папку с проектом. Структура проекта должна выглядеть следующим образом: 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593118817100.png) 

# Создание главной функции 

Создайте новый Kotlin файл с именем **Main.kt** в папке src и добавьте в нее `main()` функцию как показано ниже: 

```kotlin
fun main(args: Array<String>) { 
  println("Hello world!") 
} 
``` 
*Заметка*: Функция приложения `main()` может быть в любом Kotlin файле с любым именем, поэтому использование **Main.kt** не строго обязательно, но вы должны иметь только одну функцию `main()` в вашем проекте. 

Теперь откроем "Build" меню и нажмем "Build Project" чтобы скомпилировать ваш Kotlin код в JavaScript. IDE сгенерирует следующую папку в корне вашего проекта. 

Нам нужно упомянуть 2 выходных файла в данном проекте в **index.html**: 
* kotlin.js: это стандартная Kotlin библиотека, реализованная в JavaScript 
* bookstore.js: Это JavaScript, в который был скомпилирован Kotlin. 
*Заметка*: Оба файла уже ссылаются для вас внутри скриптами на начальный **index.html** файл, но вам возможно нужно проверить их пути на случай если вы ввели другое название проекта. 

Теперь откроем **index.html** в вашем браузере, и затем откроем консоль разработчика. Вы должны увидеть "Hello World!",как показано на скриншоте: 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593285839826.png) 

*Заметка*: Каждый раз меняя код kotlin, необходимо билдить ваш проект и затем обновлять HTML страницу чтобы увидеть изменения. 

# Вызываем JavaScript код из Kotlin 

В **index.html** файле, внутри тега со скриптом, вы найдете JavaScript функцию названную `getApiUrl()`, который возвращает URL необходимый вам для получения данных магазина в формате JSON
  
```kotlin 
<script> 
function getApiUrl(){ 
  return "https://gist.githubusercontent.com/tarek360/4578e33621011e18829bad0c8d1c8cdf/raw/06d185bebc3e14a56dfa85f53288daddd4ff6a2b/books.json"; 
} 
</script> 
```   

Есть множество путей для доступа JavaScript функции или переменно из кода Kotlin. Один из них это использование функции `js()`, которая позволяет передавать простой код JavaScript как строку. 

Добавим следуюущую строку кода в **Main.kt** файл, вне  `main()` функции. 
```kotlin 
val API_URL = js("getApiUrl()") as String 
``` 
Здесь вы передаете строку `getApiUrl()` в  `js()` функцию. Теперь `getApiUrl()` функция всегда возвращает строку, вы можете привести её безопасно к String Kotlin, и хранить как обычное значение. 

Теперь, обновим `main()` функцию для выведения значения `API_URL` свойства вместо "Hello world!" 

```kotlin 
fun main(args: Array<String>) { 
  println(API_URL) 
} 
```
Билдим проект и обновляем index.html в браузере. Вы дожны увидеть значение `API_URL` переменной, выведенной в консоль, как на скриншоте ниже 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593286796376.png) 

Теперь у вас есть URL сохраненный в `API_URL` значении, который вы используете позднее. 

Очистим `main()` функцию, чтобы подготовить её к тому, что будет происходить дальше. 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593286886581.png) 

Чтобы получить данные от сервера их отобразить их в UI, необходимо создать новый Kotlin класс для отображения одной книги. Создадим файл **Book.kt**  в **src** папке и поместит тут класс данных: 

```kotlin 
data class Book(val title: String, 
                val price: String, 
                val description: String, 
                val url: String, 
                val coverUrl: String) 
``` 
Каждая книга имеет заголовок, цену, описание, URL для страницы с деталями на сайте и ссылку на картинку обложки. 

# Создаем приложение 

Вы будете использовать простую архитектуру MVP в приложении. Класс отображению будет содержать всю бизнес логику, пока класс страницы работает как вью. Перед созаднием этих классов, создадим контракт между ними. 

*Заметка*: Если вы не имеет опыта работы с MVP, то посмотрите учебное пособие `Getting Started with MVP on Android`. 

Создадим новый Kotlin интерфейс, названный `BookStoreContract` (как обычно, в его собственном файле в папке `src`) который определяет подключение между вью и представителем. Добавим следующий код: 

```kotlin 
interface BookStoreContract { 
  interface View { 
    fun showBooks(books: List<Book>) // 1 
    fun showLoader() // 2 
    fun hideLoader() // 3 
  } 
  
  interface Presenter { 
    fun attach(view: View) // 4 
    fun loadBooks() // 5 
  } 
} 
``` 

Эта вью сможет: 
* Отображать список книг, предоставленных ей 
* Показывать индикатор загрузки пока приложение получает данные от сервера 
* Прятать индикатор загрузки. 

Что касается от представителя, он может: 
* Отображать результаты на любой вью которая предоставлена 
* Начинать загрузку данных о книгах из источника данных, в этом случае, это удаленный сервер. 
  
  
Выполнив это, вы теперь можете создать класс `BookStorePage`, и добавить в нее такой код: 

```kotlin  
class BookStorePage(private val presenter: BookStoreContract.Presenter) : BookStoreContract.View { 
  override fun showBooks(books: List<Book>) { 
  } 
  override fun showLoader() { 
  } 
  override fun hideLoader() { 
  } 
} 
``` 

Этот класс имеет конструктор с `BookStoreContract.Presenter` параметром. Он реализует `BookStoreContract.View` интерфейс с тремя необходимыми методами (пока пустыми) 

Создадим `BookStorePresenter` класс и добавим следующий код:   

```kotlin 
// 1 
class BookStorePresenter : BookStoreContract.Presenter { 
  // 2 
  private lateinit var view: BookStoreContract.View 
  // 3 
  override fun attach(view: BookStoreContract.View) { 
    this.view = view 
  } 
  // 4 
  override fun loadBooks() { 
  } 
} 
``` 

В этом классе, вы: 
* Реализовали  `BookStoreContract.Presenter` интерфейс. 
* Добавили свойство для хранения сылки на вью. 
* Реализовали метод `attach()` из `BookStoreContract.Presenter` интерфейса, и проинициализировать вью свойство из полученного параметра. 
* Реализовали `loadBooks()` метод требуемый `BookStoreContract.Presenter` интерфейсом(пока пустым). 

# Получение данных с сервера 

Вам нужен пут для получения данных с сервера. Чтоыб это сделать, добавьте следующий метод в `BookStorePresenter` класс. 

```kotlin 
// 1 
private fun getAsync(url: String, callback: (String) -> Unit) { 
  // 2 
  val xmlHttp = XMLHttpRequest() 
  // 3 
  xmlHttp.open("GET", url) 
  // 4 
  xmlHttp.onload = { 
    // 5 
    if (xmlHttp.readyState == 4.toShort() && xmlHttp.status == 200.toShort()) { 
      // 6 
      callback.invoke(xmlHttp.responseText)  
    } 
  } 
  // 7 
  xmlHttp.send() 
} 
``` 
Нажмите *option+return* на Mac или *Alt+Enter* на PC, чтобы добавить в импорт класс XMLHttpRequest. 

Пройдем по тому, что мы делаем, шаг за шагом. 
 
* Создание метода который делает сетевые запросы. Он берет URL  для получения, так же как и функция со `String` параметром, которая передать результат сетевого вызова. 
* Создаем объект `XMLHttpRequest`. `
* Указываем этот запрос посылает `HTTP GET` запрос на заданный URL. 
* Указываем `callback` который будет выполнен, когда запрос завершится.
* Проверяем если запрос имеет состояние выполнен, и если он имеет статус код 200. 
* Вызываем `callback` функцию, принимаемую как параметр, и передаем её содержание сетевого ответа как строки. 
* Вызываем `send()` чтобы произвести HTTP запрос настроенный ранее. 

Теперь, вы можете использовать вспомогательный метод для реализации `loadBooks()`: 

```kotlin 
override fun loadBooks() { 
  //1 
  view.showLoader() 
  //2 
  getAsync(API_URL) { response -> 
    //3 
    val books = JSON.parse<Array<Book>>(response) 
    //4 
    view.hideLoader() 
    //5 
    view.showBooks(books.toList()) 
  } 
} 
``` 

В этой части кода: 
* Просим вью показать загрузочный индикатор прежде, чем начать загружать данные. 
* Делаем асинхронный запрос чтобы получить данные о книгах 
* Парсим JSON ответ полученный как массив объектов данных клласа `Book`  
* Просим вью спрятать индикатор загрузки, так как вы закончили загрузку и парсинг 
* Просим вью отобразить список книг 

  

Вы можете пройтись по массиву книг и распечатать заголовок каждый книги в консоль, чтобы быть уверенным, что все работает правильно. Чтобы это сделать добавим следующие линии кода после того, как книги будут распарсены: 

  
```kotlin 
books.forEach { book -> 
  println(book.title) 
} 
``` 

Чтобы протестировать код представителя, обновим `main()` функцию для чтения: 

```kotlin 
fun main(args: Array<String>) { 
  val bookStorePresenter = BookStorePresenter() 
  val bookStorePage = BookStorePage(bookStorePresenter) 
  bookStorePresenter.attach(bookStorePage) 
  bookStorePresenter.loadBooks() 
}   
``` 

Тут мы создаем новый объект `BookStorePresenter`, и затем объект `BookStorePage`, переданные странице от объекта представителя через его конструктор. Теперь вы добавляете страницу в представителя и вызывается `loadBooks()` непосредственно на представителе. 

Сбилдим и запустим проект и обновим **index.html**. Вы должны увидеть лог как на картинке ниже: 

![](https://notepad.gasick.ru/uploads/images/gallery/2020-06/scaled-1680-/image-1593332490662.png) 

После выполнения этого тестирования, уберите цикл `forEach` с выражением `print` внутри `loadBooks()` 

*Заметка*: Если вы пытаетесь печатать саму книгу(`println(book)`), это нормально видеть только объект `Object` повторяющийся  снова и снова в выходе. Это потому что вызов `JSON.parce` создает чистый JavaScript объект вместо вызова конструктора класса Book Kotlin. 

Это значит что вы сможете читать его свойства, но любой метод ожидающий класс будет пропущен - включая  автогерированную реализацию `toSting()`. Если вам необходим более надежный метод анализа решения который сделает это правильно, вы можете изучить подробнее `kotlinx.serialization` библиотеку. 

# Создание UI 

Файл **index.html**  содержит два `div` тега с `ID`, названный `loader`  и `content`. Для начала загрузочный индикатор, который вы можете показывать пока ваше приложение загружает данные, и прячет, когда эта загрузка выполнена. Затем контейнер, в который будет добавлены все карточки книг. 

Для доступа в **DOM** элементы в вашем Kotlin коде, добавьте следующие два новых свойства в класс `BookStorePage` как показано ниже.   

```kotlin 
private val loader = document.getElementById("loader") as HTMLDivElement 
private val content = document.getElementById("content") as HTMLDivElement 
``` 

Вы всегда можете получить элемент в **DOM** по его `ID`, используя объект документа и `getElementeById()` метод, так же как это делается в JavaScript. 

Метод `getElementById()` возвращает общий `Element`, который вы можете привести к другому типу элемента если нужно.(похоже на то на как метод `findViewById()` работает в Android). 

# Меняем видимость загрузчика 

Обновим методы `showLoader()` и `hideLoader()` в `BookStorePage` следующий образом. 

```kotlin 
override fun showLoader() { 
  loader.style.visibility = "visible" 
} 
override fun hideLoader() { 
  loader.style.visibility = "hidden" 
} 
``` 

Снова, вы используете обычную **DOM** модель для изменения визуальныйх свойств элементов `visible` и `hidden`, как и трубется. 

Элемент загрузчик, видимый по умолчанию, поэтому вы должны видеть его, когда открываете страницу index.html. 

![](https://koenig-media.raywenderlich.com/uploads/2018/12/loading-1.gif) 

Проверьте ваш код и спрячьте загрузчик изменим следующим образом функцию `main()`: 

```kotlin 
fun main(args: Array<String>) { 
  val bookStorePresenter = BookStorePresenter() 
  val bookStorePage = BookStorePage(bookStorePresenter) 
  bookStorePage.hideLoader() 
} 
``` 

Вы обновили `main` функцию вызывать `hideLoader()` напрямую чтобы прятать индикатор который был до этого видим. 

Сбилдим проект и обновим **index.html** в вашем браузере. Загрузчик должен теперь исчезнуть. 

# Создание элементов книги. 

Теперь, вы создадите карточку отображения каждой книги, как показано ниже 

![](https://koenig-media.raywenderlich.com/uploads/2018/12/book_card-222x320.png) 

Создайте новый класс и назовите его `CardBuilder`. В этом классе вы создадите `HTMLElement` для представления книги, свяжите детали книги с ним, и примените CSS. Начните с класса как показано ниже: 
  
```kotlin 
class CardBuilder { 
  
  fun build(book: Book): HTMLElement { 
    // 1 
    val containerElement = document.createElement("div") as HTMLDivElement 
    val imageElement = document.createElement("img") as HTMLImageElement 
    val titleElement = document.createElement("div") as HTMLDivElement 
    val priceElement = document.createElement("div") as HTMLDivElement 
    val descriptionElement = document.createElement("div") as HTMLDivElement 
    val viewDetailsButtonElement = document.createElement("button") as HTMLButtonElement 
  
    // 2 
    bind(book = book, 
        imageElement = imageElement, 
        titleElement = titleElement, 
        priceElement = priceElement, 
        descriptionElement = descriptionElement, 
        viewDetailsButtonElement = viewDetailsButtonElement) 
  
    // 3 
    applyStyle(containerElement, 
        imageElement = imageElement, 
        titleElement = titleElement, 
        priceElement = priceElement, 
        descriptionElement = descriptionElement, 
        viewDetailsButtonElement = viewDetailsButtonElement) 
  
    // 4 
    containerElement 
        .appendChild( 
            imageElement, 
            titleElement, 
            descriptionElement, 
            priceElement, 
            viewDetailsButtonElement 
        ) 
    // 5     
    return containerElement 
  } 
  
  // 6 
  private fun Element.appendChild(vararg elements: Element) { 
    elements.forEach { 
      this.appendChild(it) 
    } 
  } 
} 
``` 

Нужно много чего еще сделать, давайте посмотрим на шаги по очереди: 

* Создать новые элементы используя `createElement()` API барузера, передавая имя HTML тэга для созадния. Для примера, используейте `div` для создания `HTMLDivElement()` и `img` чтобы создать `HTMLImageElement`. 
* Свяжите класс данных `book` c созданным `HTML` элементом. Вы скоро реализуете этот метод `bind()`  
* Примените некоторые CSS классы к `HTML` элементам. Так же реализуете `applyStyle()` метод ниже. 
* Добавите все отдельные `HTML` элементы в один контейнер. 
* Вернете контейнер, который является корневым элементов карточек. 
* Напишите расширение функции которая позволяет вам добавлять переменное количество дочерних к элементы, вместо вызова обычного `appendChild()` метода множество раз. 

# Создание данных 

Заполните элементы данными, добавьте следующий код. 

Заполнените элементы данными, добавьте следующие метод в класс `CardBuilder`: 

```kotlin 
private fun bind(book: Book, 
                 imageElement: HTMLImageElement, 
                 titleElement: HTMLDivElement, 
                 priceElement: HTMLDivElement, 
                 descriptionElement: HTMLDivElement, 
                 viewDetailsButtonElement: HTMLButtonElement) { 
  // 1 
  imageElement.src = book.coverUrl  
  // 2 
  titleElement.innerHTML = book.title 
  priceElement.innerHTML = book.price 
  descriptionElement.innerHTML = book.description 
  viewDetailsButtonElement.innerHTML = "view details" 
   
  // 3 
  viewDetailsButtonElement.addEventListener("click", { 
    window.open(book.url) 
  }) 
} 
``` 

В этом методе, вы: 
* Укажите ссылку на обложку книги как источник элемента картинки в карточке. 
* Укажите текстовое содержание для различных текстовых элементов. 
* Добавьте слушателя события `click` к элементу кнопка, которая будет вести к URL книги если нажата кнопка. 

# Применение CSS 

До сих пор отсутствует метод `applyStyle()`, который вы должны так же добавить в класс `CardBuilder`. 

```kotlin 
private fun applyStyle(containerElement: HTMLDivElement, 
                       imageElement: HTMLImageElement, 
                       titleElement: HTMLDivElement, 
                       priceElement: HTMLDivElement, 
                       descriptionElement: HTMLDivElement, 
                       viewDetailsButtonElement: HTMLButtonElement) { 
  containerElement.addClass("card", "card-shadow") 
  imageElement.addClass("cover-image") 
  titleElement.addClass("text-title", "float-left") 
  descriptionElement.addClass("text-description", "float-left") 
  priceElement.addClass("text-price", "float-left") 
  viewDetailsButtonElement.addClass("view-details", "ripple", "float-right") 
} 
``` 

Этот метода добавляет верные CSS классы, который вам нужны для стилизации карточек книг с помощью "material design". Вы можете найти эти классы уже настроенными в style.css. Для примера, тень карточки CSS класса дает "material" тень карточке контейнера, и `float-left` css класс выравнивает элементы по левой стороне. 

# Создание карточки. 

Вернемся назад в  `BookStorePage` класс и начнем использовать код создания этой карточки. Первый, добавим свойство в класс, который будет хранить объект `CardBuilder` 

```kotlin 
private val cardBuilder = CardBuilder() 
``` 

Теперь идем в `showBooks()` метод и добавялем следующий код: 

```kotlin 
books.forEach { book -> 
  val card = cardBuilder.build(book) 
  content.appendChild(card) 
} 
``` 

Этот код проходит через список книг, и для каждой книги, созадет `HTML` элемент, отображающий её. Теперь, это добавляет элемент к содержанию `div` который мы наблюдали из *DOM* ранее.  

# Отображение Страницы книжного магазина  

Вы почти закончили. Добавим следующий метод в класс `BookStorePage`. 

```kotlin 
fun show() { 
  presenter.attach(this) 
  presenter.loadBooks() 
} 
``` 

Этот код указывает текущий `BookStorePage` экземпляр как представитель вью, который может получать `callback` от него. И затем просит представителя начать загрузку книг. 

Идите в `main()` функцию и обновите, чтобы она вызывала `show()` метод в `bookStorePage`. Полный метод `main()` должен теперь выглядеть таким образом: 

```kotlin 
fun main(args: Array<String>) { 
  val bookStorePresenter = BookStorePresenter() 
  val bookStorePage = BookStorePage(bookStorePresenter) 
  bookStorePage.show() 
} 
``` 

Сбилдим проект, и обновим **index.html**. 

Вы должны увидеть загрузчик мельком, прежде чем приложение закончит загрузку книги. Затем отобразятся книжные каточки. Карточки должны иметь теперь, когда вы будете наводить на нее мышку, и кнопка "View Details" должна вас вести на нужную страницу книги. 

![](https://koenig-media.raywenderlich.com/uploads/2018/12/end_product-1.png) 

Ура! Вы воздали ваше первое web приложение на Kotlin :] 

Счастливое лицо!

# Фото бот

Сегодня наша миссия - создать фото бота. который будет посылать пользователю картинку. Это просто пример, мы не будем использовать картинки онлайн, не будет поддержки груповых чатов. Просто локально картинки. Но зато мы научимся как создавать свои клавиатуры, как посылать картинки и создавать команды.

Давайте уважать сервера телеграм

И так для начала,  давайте подготовит нашу картинку. Скачаем 5 незнакомых нам картинок. Смотрите: мы пошлем один и тот же файл пользователю много раз, поэтому сохраним наш трафик и место на жестком дисе серверов телеграм. Это чудесно что мы можем загрузить наши файлы на их сервера единожды и и просто пыслать файлы(картинки, аудио, документы, голосовые сообщения) с помощью их уникального  id. Ну что ж, теперь давайте узнаем `file_id` когда мы его отправим боту. Как обычно, создаем новый проект и создаем 2 файла: `Main.java` и `PhotoBot.java`.

Добавим следующий код в первый файл, не забываем установить библиотеку телеграм бота.

```java
import org.telegram.telegrambots.ApiContextInitializer;
import org.telegram.telegrambots.TelegramBotsApi;
import org.telegram.telegrambots.exceptions.TelegramApiException;


public class Main {
    public static void main(String[] args) {
        ApiContextInitializer.init();

        TelegramBotsApi botsApi = new TelegramBotsApi();

        try {
            botsApi.registerBot(new PhotoBot());
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
        System.out.println("PhotoBot successfully started!");
    }
}
```

Этот код зарегистрирует нашего бота и ответи "PhotoBot successfully started!", когда он будет успешно запущен. Затем, сохрханим и откроем `PhotoBot.java`. Вставляем следующий код.
Не забываем указать `username`  и `token`:

```java
import org.telegram.telegrambots.api.methods.send.SendMessage;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.exceptions.TelegramApiException;

public class PhotoBot extends TelegramLongPollingBot {
    @Override
    public void onUpdateReceived(Update update) {

        // We check if the update has a message and the message has text
        if (update.hasMessage() && update.getMessage().hasText()) {
            // Set variables
            String message_text = update.getMessage().getText();
            long chat_id = update.getMessage().getChatId();
            SendMessage message = new SendMessage() // Create a message object object
                    .setChatId(chat_id)
                    .setText(message_text);
            try {
                sendMessage(message); // Sending our message object to user
            } catch (TelegramApiException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public String getBotUsername() {
        // Return bot username
        // If bot username is @MyAmazingBot, it must return 'MyAmazingBot'
        return "PhotoBot";
    }

    @Override
    public String getBotToken() {
        // Return bot token from BotFather
        return "12345:qwertyuiopASDGFHKMK";
    }
}
```

Теперь обновим наш `onUpdateReceived` метод. Мы хотим посылать `file_id` картинки которая отправляется боту. Давайте проверим содержит ли сообщение объект с картинкой:

```java
@Override
public void onUpdateReceived(Update update) {

    // We check if the update has a message and the message has text
    if (update.hasMessage() && update.getMessage().hasText()) {
        // Set variables
        String message_text = update.getMessage().getText();
        long chat_id = update.getMessage().getChatId();
        SendMessage message = new SendMessage() // Create a message object object
                    .setChatId(chat_id)
                    .setText(message_text);
        try {
            sendMessage(message); // Sending our message object to user
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    } else if (update.hasMessage() && update.getMessage().hasPhoto()) {
        // Message contains photo
    }
}
```

Мы хотим чтобы наш бот отправял `file_id`  картинки. Давайте сделаем это:

```java
else if (update.hasMessage() && update.getMessage().hasPhoto()) {
    // Message contains photo
    // Set variables
    long chat_id = update.getMessage().getChatId();

    // Array with photo objects with different sizes
    // We will get the biggest photo from that array
    List<PhotoSize> photos = update.getMessage().getPhoto();
    // Know file_id
    String f_id = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getFileId();
    // Know photo width
    int f_width = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getWidth();
    // Know photo height
    int f_height = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getHeight();
    // Set photo caption
    String caption = "file_id: " + f_id + "\nwidth: " + Integer.toString(f_width) + "\nheight: " + Integer.toString(f_height);
    SendPhoto msg = new SendPhoto()
                    .setChatId(chat_id)
                    .setPhoto(f_id)
                    .setCaption(caption);
    try {
        sendPhoto(msg); // Call method to send the photo with caption
    } catch (TelegramApiException e) {
        e.printStackTrace();
    }
}
```

Взгляните:
[![](https://notepad.gasick.ru/uploads/images/gallery/2021-02/scaled-1680-/image-1612713525929.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-02/image-1612713525929.png)

Чудесно!  Теперь мы знаем `file_id` картинки и мы можемп посылать её с помощью  `file_id`. Давайте заставим нашего бота отвечать этой картинкой на команду `/pic`.

```java
if (update.hasMessage() && update.getMessage().hasText()) {
    // Set variables
    String message_text = update.getMessage().getText();
    long chat_id = update.getMessage().getChatId();
    if (message_text.equals("/start")) {
        // User send /start
        SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText(message_text);
        try {
            sendMessage(message); // Sending our message object to user
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    } else if (message_text.equals("/pic")) {
        // User sent /pic
        SendPhoto msg = new SendPhoto()
                        .setChatId(chat_id)
                        .setPhoto("AgADAgAD6qcxGwnPsUgOp7-MvnQ8GecvSw0ABGvTl7ObQNPNX7UEAAEC")
                        .setCaption("Photo");
                try {
                    sendPhoto(msg); // Call method to send the photo
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
    } else {
        // Unknown command
        SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText("Unknown command");
        try {
            sendMessage(message); // Sending our message object to user
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
    }
}
```

Теперь бот посылает картинку так:
[![/pic command](https://notepad.gasick.ru/uploads/images/gallery/2021-02/scaled-1680-/image-1612713657275.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-02/image-1612713657275.png)

И даже говорить что не знает какие-то команды:

[![Unknown command](https://notepad.gasick.ru/uploads/images/gallery/2021-02/scaled-1680-/image-1612713686805.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-02/image-1612713686805.png)

Давайте теперь вгзлянем на `ReplyKeyboardMarkup`. Мы создадим свою клавиатуру как показано ниже:

[![Custom keyboards preview](https://notepad.gasick.ru/uploads/images/gallery/2021-02/scaled-1680-/image-1612713723072.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-02/image-1612713723072.png)


Ну чтож, туперь вы знаете как научить нашего бота распознавать команды. Давайте создадим другой `if` для команды `/markup`.

	Помните! Нажатие на кнопку отправляет боту текст этой кнопки. Для примера, если мы вставим "Hello" текст в кнопку, то когда мы её нажмем, она отправит текст "Hello" боту.

```java
else if (message_text.equals("/markup")) {
    SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText("Here is your keyboard");
    // Create ReplyKeyboardMarkup object
    ReplyKeyboardMarkup keyboardMarkup = new ReplyKeyboardMarkup();
    // Create the keyboard (list of keyboard rows)
    List<KeyboardRow> keyboard = new ArrayList<>();
    // Create a keyboard row
    KeyboardRow row = new KeyboardRow();
    // Set each button, you can also use KeyboardButton objects if you need something else than text
    row.add("Row 1 Button 1");
    row.add("Row 1 Button 2");
    row.add("Row 1 Button 3");
    // Add the first row to the keyboard
    keyboard.add(row);
    // Create another keyboard row
    row = new KeyboardRow();
    // Set each button for the second line
    row.add("Row 2 Button 1");
    row.add("Row 2 Button 2");
    row.add("Row 2 Button 3");
    // Add the second row to the keyboard
    keyboard.add(row);
    // Set the keyboard to the markup
    keyboardMarkup.setKeyboard(keyboard);
    // Add it to the message
    message.setReplyMarkup(keyboardMarkup);
    try {
        sendMessage(message); // Sending our message object to user
    } catch (TelegramApiException e) {
        e.printStackTrace();
    }
}
```
Отлично! Теперь научим бота реагировать на кнопки:

```java
else if (message_text.equals("Row 1 Button 1")) {
    SendPhoto msg = new SendPhoto()
                .setChatId(chat_id)
                .setPhoto("AgADAgAD6qcxGwnPsUgOp7-MvnQ8GecvSw0ABGvTl7ObQNPNX7UEAAEC")
                .setCaption("Photo");
    try {
        sendPhoto(msg); // Call method to send the photo
    } catch (TelegramApiException e) {
        e.printStackTrace();
    }
}
```
Когда пользователь нажмет на "Row 1 Button 1", бот в ответ отправит ему `file_id` картинки.

[![Bot sends photo from keyboard](https://notepad.gasick.ru/uploads/images/gallery/2021-02/scaled-1680-/image-1612714030881.png)](https://notepad.gasick.ru/uploads/images/gallery/2021-02/image-1612714030881.png)

Добавим функцию "Убрать клавиатуру", когда человек отправляет команду `/hide` боту. Это может быть с помощью `ReplyMarkupRemove`.

```java
else if (message_text.equals("/hide")) {
    SendMessage msg = new SendMessage()
                        .setChatId(chat_id)
                        .setText("Keyboard hidden");
    ReplyKeyboardRemove keyboardMarkup = new ReplyKeyboardRemove();
    msg.setReplyMarkup(keyboardMarkup);
    try {
        sendMessage(msg); // Call method to send the photo
    } catch (TelegramApiException e) {
        e.printStackTrace();
    }
}
```
Вот код для наших файлов. вы так же можете найти все ресурсы в репе на GitHub.


src/Main.java
```java
import org.telegram.telegrambots.ApiContextInitializer;
import org.telegram.telegrambots.TelegramBotsApi;
import org.telegram.telegrambots.exceptions.TelegramApiException;


public class Main {
    public static void main(String[] args) {
        ApiContextInitializer.init();

        TelegramBotsApi botsApi = new TelegramBotsApi();

        try {
            botsApi.registerBot(new PhotoBot());
        } catch (TelegramApiException e) {
            e.printStackTrace();
        }
        System.out.println("PhotoBot successfully started!");
    }
}
```
src/PhotoBot.java
```java
import org.telegram.telegrambots.api.methods.send.SendMessage;
import org.telegram.telegrambots.api.methods.send.SendPhoto;
import org.telegram.telegrambots.api.objects.PhotoSize;
import org.telegram.telegrambots.api.objects.Update;
import org.telegram.telegrambots.api.objects.replykeyboard.ReplyKeyboardMarkup;
import org.telegram.telegrambots.api.objects.replykeyboard.ReplyKeyboardRemove;
import org.telegram.telegrambots.api.objects.replykeyboard.buttons.KeyboardRow;
import org.telegram.telegrambots.bots.TelegramLongPollingBot;
import org.telegram.telegrambots.exceptions.TelegramApiException;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class PhotoBot extends TelegramLongPollingBot {
    @Override
    public void onUpdateReceived(Update update) {

        // We check if the update has a message and the message has text
        if (update.hasMessage() && update.getMessage().hasText()) {
            // Set variables
            String message_text = update.getMessage().getText();
            long chat_id = update.getMessage().getChatId();
            if (message_text.equals("/start")) {
                SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText(message_text);
                try {
                    sendMessage(message); // Sending our message object to user
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            } else if (message_text.equals("/pic")) {
                SendPhoto msg = new SendPhoto()
                        .setChatId(chat_id)
                        .setPhoto("AgADAgAD6qcxGwnPsUgOp7-MvnQ8GecvSw0ABGvTl7ObQNPNX7UEAAEC")
                        .setCaption("Photo");
                try {
                    sendPhoto(msg); // Call method to send the photo
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            } else if (message_text.equals("/markup")) {
                SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText("Here is your keyboard");
                // Create ReplyKeyboardMarkup object
                ReplyKeyboardMarkup keyboardMarkup = new ReplyKeyboardMarkup();
                // Create the keyboard (list of keyboard rows)
                List<KeyboardRow> keyboard = new ArrayList<>();
                // Create a keyboard row
                KeyboardRow row = new KeyboardRow();
                // Set each button, you can also use KeyboardButton objects if you need something else than text
                row.add("Row 1 Button 1");
                row.add("Row 1 Button 2");
                row.add("Row 1 Button 3");
                // Add the first row to the keyboard
                keyboard.add(row);
                // Create another keyboard row
                row = new KeyboardRow();
                // Set each button for the second line
                row.add("Row 2 Button 1");
                row.add("Row 2 Button 2");
                row.add("Row 2 Button 3");
                // Add the second row to the keyboard
                keyboard.add(row);
                // Set the keyboard to the markup
                keyboardMarkup.setKeyboard(keyboard);
                // Add it to the message
                message.setReplyMarkup(keyboardMarkup);
                try {
                    sendMessage(message); // Sending our message object to user
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            } else if (message_text.equals("Row 1 Button 1")) {
                SendPhoto msg = new SendPhoto()
                        .setChatId(chat_id)
                        .setPhoto("AgADAgAD6qcxGwnPsUgOp7-MvnQ8GecvSw0ABGvTl7ObQNPNX7UEAAEC")
                        .setCaption("Photo");

                try {
                    sendPhoto(msg); // Call method to send the photo
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            } else if (message_text.equals("/hide")) {
                SendMessage msg = new SendMessage()
                        .setChatId(chat_id)
                        .setText("Keyboard hidden");
                ReplyKeyboardRemove keyboardMarkup = new ReplyKeyboardRemove();
                msg.setReplyMarkup(keyboardMarkup);
                try {
                    sendMessage(msg); // Call method to send the photo
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            } else {
                SendMessage message = new SendMessage() // Create a message object object
                        .setChatId(chat_id)
                        .setText("Unknown command");
                try {
                    sendMessage(message); // Sending our message object to user
                } catch (TelegramApiException e) {
                    e.printStackTrace();
                }
            }
        } else if (update.hasMessage() && update.getMessage().hasPhoto()) {
            // Message contains photo
            // Set variables
            long chat_id = update.getMessage().getChatId();

            List<PhotoSize> photos = update.getMessage().getPhoto();
            String f_id = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getFileId();
            int f_width = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getWidth();
            int f_height = photos.stream()
                    .sorted(Comparator.comparing(PhotoSize::getFileSize).reversed())
                    .findFirst()
                    .orElse(null).getHeight();
            String caption = "file_id: " + f_id + "\nwidth: " + Integer.toString(f_width) + "\nheight: " + Integer.toString(f_height);
            SendPhoto msg = new SendPhoto()
                    .setChatId(chat_id)
                    .setPhoto(f_id)
                    .setCaption(caption);
            try {
                sendPhoto(msg); // Call method to send the message
            } catch (TelegramApiException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public String getBotUsername() {
        // Return bot username
        // If bot username is @MyAmazingBot, it must return 'MyAmazingBot'
        return "PhotoBot";
    }

    @Override
    public String getBotToken() {
        // Return bot token from BotFather
        return "12345:qwertyuiopASDGFHKMK";
    }
}
```

Теперь вы можете создавать и скрывать клавиатуру, создавать свои каоманды и отправлять картинки с помощью `file_id`.