Опыт разработки мини-приложения для Telegram: создание NeVerRLandPoker

Привет, друзья! 🙌

Недавно я закончил работу над своим первым мини-приложением для Telegram, и в этой статье хочу поделиться опытом его разработки. Как веб-разработчик, который до этого работал только с веб-приложениями, для меня создание бота и мини-приложения в Telegram стало чем-то новым (но честно говоря, различий с вебом оказалось меньше, чем ожидалось).

О приложении NeVerRLandPoker

Идея моего мини-приложения зародилась довольно давно. Как пет-проект я решил создать сервер для покера на Node.js (хотя сам я больше PHP-шник, но в этот раз Node.js оказался предпочтительнее) и клиентскую часть на Vue.js (мой любимый фреймворк). Так и появилась NeVerRLandPoker — рейтинговая игра в Техасский Холдем, в которой каждый ход может стать решающим.

Почему я выбрал разработку мини-приложения для Telegram?

Мини-приложения Telegram — это удобная платформа для создания небольших интерактивных приложений, и вот почему:

  1. Простота доступа. Пользователю не нужно ничего скачивать — достаточно открыть бота, и сервис или игра уже на экране.
  2. Широкая аудитория. Telegram обладает огромной пользовательской базой, что делает его отличной площадкой для запуска игровых и развлекательных приложений.

Чему я научился в процессе разработки мини-приложения?

  1. Интуитивный интерфейс. Одной из главных задач стало упрощение интерфейса для игры через Telegram. Поскольку экранные элементы в Telegram мини-приложениях ограничены, я убрал лишние функции, оставив только то, что важно для геймплея в покерной игре с рейтингами, такой как NeVerRLandPoker.
  2. API Telegram. Важной частью разработки стало понимание возможностей Telegram API, особенно аутентификации пользователей. Раньше я использовал стандартное окно регистрации для получения логина и пароля. Теперь же Telegram сам передает информацию о пользователе, и я использую её для создания токена аутентификации в сокетах.
  3. Обратная связь пользователей. Telegram позволяет мгновенно получать фидбек от пользователей. Это очень помогло в улучшении приложения: игроки могут прямо через бота оставить свои комментарии, и я могу оперативно вносить изменения.

Как адаптировать веб-приложение для Telegram?

Процесс адаптации моего покерного приложения для Telegram оказался проще, чем я думал:

  1. Зарегистрировать бота через BotFather — это стандартный шаг для создания любого Telegram-бота.
  2. В Bot Settings > Menu Button указать ссылку на веб-страницу мини-приложения.
  3. Подкорректировать код для интеграции с Telegram API.

Изменения в коде

Раньше у меня было окно регистрации, которое отправляло логин и пароль для получения токена, используемого для аутентификации в сокетах. Сейчас Telegram API предоставляет информацию о пользователе, и я использую её для создания токена.

Кроме того, для удобства я использую флаг isClosingConfirmationEnabled, чтобы убедиться, что пользователь не случайно вышел из приложения. А метод expand() позволяет разворачивать окно на весь экран.

Что получилось в итоге?

Адаптация моей игры к Telegram оказалась не только простой, но и интересной. Моё мини-приложение — NeVerRLandPoker — это рейтинговая игра в Техасский Холдем с использованием анте, что делает игру динамичной и захватывающей. Важной частью игры стала рейтинговая система, которая мотивирует игроков соревноваться за место в топе.

Само приложение доступно в Telegram через бота. Всё, что вам нужно, — это просто открыть бота и начать играть. Присоединяйтесь и попробуйте свои силы в покере!

Совет разработчикам мини-приложений для Telegram

Если вы собираетесь создавать мини-приложение для Telegram, помните, что пользователи ценят простоту и доступность. Чем проще пользователю начать использовать ваше приложение, тем лучше. Не перегружайте интерфейс, и ваше приложение получит положительный отклик.

Vue каратэ: основные приемчики

Привет, поговорим о vue-приемчиках… Под приемами я подразумеваю способы передачи данных между компонентами. Сразу скажу, что это не все приемы, а лишь те которые я использую и о которых помню на момент написания статьи. Погнали!

Events

Наверное самая простая вещь в фреймворке которая полностью идентична js-концепции событий. К примеру событие клика по кнопке

<!-- HTML -->
<button onclick="func"></button>
<!-- Vue -->
<button v-on:click="func"></button>
<!-- работает идеентично -->
<button @click="func"></button>

также, легко можно создать кастомный ивент и передать в него значение любого типа

<!-- child-component -->
<button @click="$emit('custom-event',{})"></button>
<!-- parent-compponent -->
<child-component @custom-event="func"/>

Однако это работает только от ребенка к родителю

Управление ребенком

Здесь ивенты уже как-бы “не алё”, на помощь приходят либо ссылки либо пропсы

Начнем из менее лаконичного способа – ссылок. Выглядит это так(родитель)

<template>
  <div>
    <button @click="toggle">Click me!</button>
    <child-component ref="child"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  methods:{
    toggle(){
      this.$refs.child.collapse = !this.$refs.child.collapse
    }
  }
}
</script>

ребёнок

<template>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  data:function () {
    return {collapse:true}
  }
}
</script>

<style scoped>

</style>

Здесь атрибут ref создает ссылку на ребёнка, которая предоставляет доступ к свойствам и методам вложенного компонента.

Более правильно(как по мне) будет использование пропсов:

<template>
  <div>
    <button @click="toggle">Click me!</button>
    <child-component :collapse="collapse"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  data:function () {
    return {collapse:true}
  },
  methods:{
    toggle(){
      this.collapse = !this.collapse
    }
  }
}
</script>
<template>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  props:{
    collapse:Boolean
  }
}
</script>

<style scoped>

</style>

тут свойство collapse попадает в дату родителя, а в дочернем копроненте переносится в пропс.

Но у меня был третий вариант: тогл происходил внутри дочерних, а в родителе была кнопка “свернуть все”. Вот мое решение

<template>
  <div>
    <button @click="toggle">Close!</button>
    <child-component :close="close" @open="open"/>
  </div>
</template>

<script>
import ChildComponent from '@/components/ChildComponent'

export default {
  name: 'App',
  components: {
    ChildComponent
  },
  data:function () {
    return {close:false}
  },
  methods:{
    toggle(){
      this.close = true
    },
    open(){
      this.close = false
    }
  }
}
</script>
<template>
  <div>
    <button @click="toggle">Toggle</button>
  </div>
  <template v-if="collapse">
    Collapse
  </template>
  <template v-else>
    Expand
  </template>
</template>

<script>
export default {
  name: 'ChildComponent',
  data:function () {
    return {collapse:true}
  },
  props:{
    close:Boolean
  },
  methods:{
    toggle(){
      this.collapse = !this.collapse
      this.$emit('open')
    }
  },
  watch:{
    close:function (old) {
      if (old)
        this.collapse = true
    }
  }
}
</script>

Суть его в том, чтоб создать пропс и поставить его под наблюдение в watch. В таком варианте, как я навел, проще было б не лезть в дитя, а просто управлять по ссылке; но дело было сложнее и с ссылками не вариант. И еще – при тогле в вложенном компоненте , я емичу событие для обнуления в родитель.

Интересный случай с таблицей. Обработка форм

Задача была построить таблицу с чекбоксами разных настроек. Моя первая итерация данного компонента

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" @change="change(i)">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',
  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ],
      i:0
    }
  },
  methods:{
    change(i){
      this.settings[i].enabled = !this.settings[i].enabled
    }
  }
}
</script>

и это работает, но у меня возникла мысль – а как получить содержимое если это textarea? Кароч, правильный путь – забыть о change и тп, использовать директиву v-model

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" v-model="setting.enabled">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',
  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ]
    }
  },
}
</script>

но все же

<template>
  <table>
    <thead>
    <tr>
      <th>Name</th>
      <th>Enabled</th>
    </tr>
    </thead>
    <template v-for="(setting,i) in settings" :key="i">
      <tr>
        <td>{{setting.title}}</td>
        <td>
          <input type="checkbox" :checked="setting.enabled" @change="setting.enabled = $event.target.checked">
        </td>
      </tr>
    </template>
  </table>
</template>

<script>
export default {
  name: 'App',

  data:function () {
    return {
      settings:[
        {title:'setting 1',enabled:true},
        {title:'setting 2',enabled:false},
        {title:'setting 3',enabled:true},
      ]
    }
  },
}
</script>

для тех, кому v-model не угодил(в комментах пишите причину – может я чего-то не знаю)…