В этой статье мы создадим бота и интегрируем его в группу VK на Python 3.x
Для тех, кто хочет написать простого бота для своего сообщества, умеющего определять команды и выводить соответствующий ответ
Начнем мы с создания бота, а именно группы в ВК.
Для это нужно зайти в «группы» → «создать сообщество».
Выберите любой тип сообщества и введите название, тематику группы.
На открывшейся странице настроек, выберите «Работа с API».
Далее, необходимо создать API-ключ.
Затем выберите нужные вам параметры с доступом для вашего API-ключа.
Скорее всего, вам придётся подтверждать действие в ВК с помощью мобильного телефона. Затем скопируйте полученный API-ключ где-нибудь в файл. Он нам еще понадобится.
Затем нужно разрешить сообщения. Для этого переходим в «сообщения» и включаем их. Также включим «Возможности ботов» в «Сообщения» -> «Настройки для бота».
Там же разрешим добавить сообщество в группы, если мы хотим чтобы бот мог получать сообщения из группы.
Для работы с Long Poll API используем библиотеку vk_api. Установить его можно через pip.
Перед работой сохраним наш API-токен в файл config.py оттуда мы будем загружать наш ключ.
Создадим наш первый скрипт. Назовем server.py, который будет основным скриптом сервера.
Импортируем нужные нам модули:
Создадим класс-сервер:
Теперь создадим файл server_manager.py, в котором будет управлять разными серверами. Пока для тестов пропишем лишь вызов класса Server:
Если все сделано правильно, то бот пришлет нам личное сообщение.
Теперь добавим бота в группу и научим его обрабатывать сообщения.
Для добавления бота в группу нужно нажать «Пригласить в беседу» в правом меню сообщества.
Добавим боту функцию start, после вызова которой он начнет «слушать» сообщения через Long Poll (не забудьте добавить разрешения в типы событий в разделе «Работа с API» -> «Long Poll API» и поставить последнюю версию):
Запустим его через server_manager.py:
Теперь, если напишем сообщение в группу, то сможем увидеть объект event'a:
Также, если мы напишем в личные сообщения:
Из этих данных нам следует обратить внимание на type, object.from_id, object.id, object.peer_id, object.text. Данные полученные из сообщений и из группы ничем сильно не отличаются, кроме object.peer_id и object.id.
Если присмотреться, object.id у всех сообщений из группы равен 0, а сообщения из личных нет. Таким образом, можно разделить сообщения получаемые из группы и из личных.
Обработаем полученные данные внутри класса Server:
Напишем боту два сообщения: один из группы, один в личку. Тогда получим:
Как мы видим, vk_api позволяет нам легко использовать методы VK API. К примеру, сейчас мы использовали метод users.get
Список всех методов доступен по ссылке: vk.com/dev/methods
Советую изучить и поэкспериментировать с методами, которые вас заинтересуют. Благо VK предоставило нам очень хорошую документацию, еще и на русском языке.
Чтобы закрепить материал, давайте добавим функцию отправки сообщения через метод messages.send:
<peer_id> — идентификатор назначения. Чтобы ответить на чье-то сообщение, в качестве параметра peer_id укажем event.object.peer_id. То есть, пошлем сообщение туда, откуда пришел запрос.
Изменим метод start:
Теперь, если бот примет сообщение, то он нам ответит в таком стиле:
Создайте функцию, которая принимает параметр peer_id и посылает пользователю фотографию загруженную в сообщество. Полезный док: vk.com/dev/messages.send
Вот и все основы. Главное, научиться пользоваться vk_api, используя различные методы, весь их список: vk.com/dev/methods. Если вы научитесь работать с документацией VK API, то сможете создавать ботов различной сложности и назначений. Пример моего бота для учебной группы: github.com/AppLoidx/GroupAssistant/tree/master
Создадим commander.py, который будет принимать команды и возвращать ответ, передаваемый пользователю Vk:
«Слушаем» сервер Long Poll и получаем сообщение пользователя ->
Передаем сообщение в Commander.input() -> Определяем режим -> Определяем команду ->
Возвращаем ответ -> Передаем пользователю
Чтобы определить режим и команду, создадим два файла command_enum.py и mode_enum.py. С помощью них мы будем определять режимы и команды через методы класса Enum:
Для смены режимов используем [слэш("/")+<имя_режима>], а все остальные команды примем как команды.
Реализуем это в Commander.py:
Весь код доступен на гитхабе: github.com/AppLoidx/VkLongPollBot
Это очень легкий процесс, осложнения могут быть вызваны, когда мы будем изменять клавиатуру под определенную сигнатуру команд, которая отличается для каждого режима.
Чтобы добавить в окне диалогов клавиатуру, необходимо в методе messages.send указать параметр keyboard, принимающий json. Выглядит это следующим образом:
Или же можно передать клавиатуру прямо с файла .json:
Документация: vk.com/dev/bots_docs_3?f=4.%2BКлавиатуры%2Bдля%2Bботов
Рассмотрим на примере нашей программы, добавив клавиатуру.
Для начала создадим файл keyboard.json:
Чтобы убрать клавиатуры необходимо передать json с пустым buttons:
Переопределим send_message в server.py:
И также в методе start:
В результате получим:

Не стоит использовать голый листинг исходников представленных здесь, они использованы лишь для того чтобы вы лучше понимали что происходит под капотом. Разумеется, они все юзабельны и можно использовать их по частям.
Лично я использовал такого бота для группового ассистента, который умел:
Проект на гитхабе
Исходники представленные здесь
Для кого эта статья?
Для тех, кто хочет написать простого бота для своего сообщества, умеющего определять команды и выводить соответствующий ответ
Основные стадии
- Для начала создадим бота в ВК, настроим API-ключ и включим Long Poll API
- Настроим Long Poll
- Добавим определения команд, которые будет обрабатывать бот
- Создадим ассоциации команд
- Добавим клавиатуру
Создание группы-бота
Начнем мы с создания бота, а именно группы в ВК.
Для это нужно зайти в «группы» → «создать сообщество».
Выберите любой тип сообщества и введите название, тематику группы.
На открывшейся странице настроек, выберите «Работа с API».
Далее, необходимо создать API-ключ.
Затем выберите нужные вам параметры с доступом для вашего API-ключа.
Скорее всего, вам придётся подтверждать действие в ВК с помощью мобильного телефона. Затем скопируйте полученный API-ключ где-нибудь в файл. Он нам еще понадобится.
Затем нужно разрешить сообщения. Для этого переходим в «сообщения» и включаем их. Также включим «Возможности ботов» в «Сообщения» -> «Настройки для бота».
Там же разрешим добавить сообщество в группы, если мы хотим чтобы бот мог получать сообщения из группы.
Настройка Long Poll
Для работы с Long Poll API используем библиотеку vk_api. Установить его можно через pip.
Перед работой сохраним наш API-токен в файл config.py оттуда мы будем загружать наш ключ.
Создадим наш первый скрипт. Назовем server.py, который будет основным скриптом сервера.
Импортируем нужные нам модули:
import vk_api.vk_api from vk_api.bot_longpoll import VkBotLongPoll from vk_api.bot_longpoll import VkBotEventType
Создадим класс-сервер:
class Server: def __init__(self, api_token, group_id, server_name: str="Empty"): # Даем серверу имя self.server_name = server_name # Для Long Poll self.vk = vk_api.VkApi(token=api_token) # Для использования Long Poll API self.long_poll = VkBotLongPoll(self.vk, group_id) # Для вызова методов vk_api self.vk_api = self.vk.get_api() def send_msg(self, send_id, message): """ Отправка сообщения через метод messages.send :param send_id: vk id пользователя, который получит сообщение :param message: содержимое отправляемого письма :return: None """ self.vk_api.messages.send(peer_id=send_id, message=message) def test(self): # Посылаем сообщение пользователю с указанным ID self.send_msg(255396611, "Привет-привет!")
Теперь создадим файл server_manager.py, в котором будет управлять разными серверами. Пока для тестов пропишем лишь вызов класса Server:
# Импортируем созданный нами класс Server from server import Server # Получаем из config.py наш api-token from config import vk_api_token server1 = Server(vk_api_token, 172998024, "server1") # vk_api_token - API токен, который мы ранее создали # 172998024 - id сообщества-бота # "server1" - имя сервера server1.test()
Важно!
Бот может писать сообщения только тем пользователям, ��оторые разрешили боту присылать сообщения. Сделать это можно на странице сообщества или же первым написать боту
Если все сделано правильно, то бот пришлет нам личное сообщение.
Теперь добавим бота в группу и научим его обрабатывать сообщения.
Для добавления бота в группу нужно нажать «Пригласить в беседу» в правом меню сообщества.
Добавим боту функцию start, после вызова которой он начнет «слушать» сообщения через Long Poll (не забудьте добавить разрешения в типы событий в разделе «Работа с API» -> «Long Poll API» и поставить последнюю версию):
def start(self): for event in self.long_poll.listen(): print(event)
Запустим его через server_manager.py:
server1.start()
Теперь, если напишем сообщение в группу, то сможем увидеть объект event'a:
<<class 'vk_api.bot_longpoll.VkBotMessageEvent'>({'type': 'message_new', 'object': {'date': 1541273151, 'from_id': 25599999999, 'id': 0, 'out': 0, 'peer_id': 2000000001, 'text': '[club172998024|bot in da Vk] this is a text!', 'conversation_message_id': 187, 'fwd_messages': [], 'important': False, 'random_id': 0, 'attachments': [], 'is_hidden': False}, 'group_id': 172998024})>
Также, если мы напишем в личные сообщения:
<<class 'vk_api.bot_longpoll.VkBotMessageEvent'>({'type': 'message_new', 'object': {'date': 1541273238, 'from_id': 25599999999, 'id': 47, 'out': 0, 'peer_id': 255396611, 'text': 'это личное сообщение', 'conversation_message_id': 47, 'fwd_messages': [], 'important': False, 'random_id': 0, 'attachments': [], 'is_hidden': False}, 'group_id': 172998024})>
Из этих данных нам следует обратить внимание на type, object.from_id, object.id, object.peer_id, object.text. Данные полученные из сообщений и из группы ничем сильно не отличаются, кроме object.peer_id и object.id.
Если присмотреться, object.id у всех сообщений из группы равен 0, а сообщения из личных нет. Таким образом, можно разделить сообщения получаемые из группы и из личных.
Обработаем полученные данные внутри класса Server:
def start(self): for event in self.long_poll.listen(): # Слушаем сервер # Пришло новое сообщение if event.type == VkBotEventType.MESSAGE_NEW: print("Username: " + self.get_user_name(event.object.from_id)) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") def get_user_name(self, user_id): """ Получаем имя пользователя""" return self.vk_api.users.get(user_id=user_id)[0]['first_name'] def get_user_city(self, user_id): """ Получаем город пользователя""" return self.vk_api.users.get(user_id=user_id, fields="city")[0]["city"]['title']
Напишем боту два сообщения: один из группы, один в личку. Тогда получим:
Username: Артур
From: Санкт-Петербург
Text: [club172998024|@club172998024] this is a message from group
Type: group message
—
Username: Артур
From: Санкт-Петербург
Text: this is a private message
Type: private message
—
Примечание
Как вы могли заметить перед сообщением в группе есть [club172998024|@club172998024], для правильной обработки команды следует избавиться от всего содержимого в квадратных скобках, либо разрешить боту доступ ко всей переписке
Как мы видим, vk_api позволяет нам легко использовать методы VK API. К примеру, сейчас мы использовали метод users.get
Список всех методов доступен по ссылке: vk.com/dev/methods
Советую изучить и поэкспериментировать с методами, которые вас заинтересуют. Благо VK предоставило нам очень хорошую документацию, еще и на русском языке.
Чтобы закрепить материал, давайте добавим функцию отправки сообщения через метод messages.send:
def send_message(self, peer_id, message): self.vk_api.messages.send(peer_id=peer_id, message=message)
<peer_id> — идентификатор назначения. Чтобы ответить на чье-то сообщение, в качестве параметра peer_id укажем event.object.peer_id. То есть, пошлем сообщение туда, откуда пришел запрос.
Изменим метод start:
def start(self): for event in self.long_poll.listen(): # Слушаем сервер # Пришло новое сообщение if event.type == VkBotEventType.MESSAGE_NEW: username = self.get_user_name(event.object.from_id) print("Username: " + username) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") self.send_message(event.object.peer_id, f"{username}, я получил ваше сообщение!")
Теперь, если бот примет сообщение, то он нам ответит в таком стиле:
Артур, я получил ваше сообщение!
Весь код
server.py
import vk_api.vk_api from vk_api.bot_longpoll import VkBotLongPoll from vk_api.bot_longpoll import VkBotEventType class Server: def __init__(self, api_token, group_id, server_name: str="Empty"): # Даем серверу имя self.server_name = server_name # Для Long Poll self.vk = vk_api.VkApi(token=api_token) # Для использоания Long Poll API self.long_poll = VkBotLongPoll(self.vk, group_id, wait=20) # Для вызова методов vk_api self.vk_api = self.vk.get_api() def send_msg(self, send_id, message): """ Отправка сообщения через метод messages.send :param send_id: vk id пользователя, который получит сообщение :param message: содержимое отправляемого письма :return: None """ self.vk_api.messages.send(peer_id=send_id, message=message) def test(self): self.send_msg(255396611, "Привет-привет!") def start(self): for event in self.long_poll.listen(): # Слушаем сервер # Пришло новое сообщение if event.type == VkBotEventType.MESSAGE_NEW: username = self.get_user_name(event.object.from_id) print("Username: " + username) print("From: " + self.get_user_city(event.object.from_id)) print("Text: " + event.object.text) print("Type: ", end="") if event.object.id > 0: print("private message") else: print("group message") print(" --- ") self.send_message(event.object.peer_id, f"{username}, я получил ваше сообщение!") def get_user_name(self, user_id): """ Получаем имя пользователя""" return self.vk_api.users.get(user_id=user_id)[0]['first_name'] def get_user_city(self, user_id): """ Получаем город пользователя""" return self.vk_api.users.get(user_id=user_id, fields="city")[0]["city"]['title'] def send_message(self, peer_id, message): self.vk_api.messages.send(peer_id=peer_id, message=message)
server_manager.py
# Импортируем созданный нами класс Server from server import Server # Получаем из config.py наш api-token from config import vk_api_token server1 = Server(vk_api_token, 172998024, "server1") server1.start()
Задачка для закрепления материала:
Создайте функцию, которая принимает параметр peer_id и посылает пользователю фотографию загруженную в сообщество. Полезный док: vk.com/dev/messages.send
Решение
Сначала, загрузим фото в группу и открыв её в ВК рассмотрим ссылку:
Нас интересует лишь выделенная часть: photo-172998024_456239017. Передадим её в качестве аргумента метода messages.send:
Добавим его в метод start и получим:

vkcom/club172998024?z=photo-172998024_456239017%2Falbum-172998024_256250731
Нас интересует лишь выделенная часть: photo-172998024_456239017. Передадим её в качестве аргумента метода messages.send:
def send_img(self, peer_id): self.vk_api.messages.send(peer_id=peer_id, attachment="photo-172998024_456239017")
Добавим его в метод start и получим:

Вот и все основы. Главное, научиться пользоваться vk_api, используя различные методы, весь их список: vk.com/dev/methods. Если вы научитесь работать с документацией VK API, то сможете создавать ботов различной сложности и назначений. Пример моего бота для учебной группы: github.com/AppLoidx/GroupAssistant/tree/master
Теперь приступим к созданию логики бота
Создадим commander.py, который будет принимать команды и возвращать ответ, передаваемый пользователю Vk:
class Commander: def __init__(self, vk_api, user_id): self.vk_api = vk_api self.user_id = user_id def input(self, msg): """ Функция принимающая сообщения пользователя :param msg: Сообщение :return: Ответ пользователю, отправившему сообщение """ pass
Построим архитектуру нашей программы:
«Слушаем» сервер Long Poll и получаем сообщение пользователя ->
Передаем сообщение в Commander.input() -> Определяем режим -> Определяем команду ->
Возвращаем ответ -> Передаем пользователю
Чтобы определить режим и команду, создадим два файла command_enum.py и mode_enum.py. С помощью них мы будем определять режимы и команды через методы класса Enum:
command_enum.py:
from enum import Enum class Command(Enum): """ weather """ weather = ["weather", "погода"] """ myanimelist """ anime_top = ["top anime", "топ аниме"]
mode_enum.py:
from enum import Enum class Mode(Enum): default = ["Обычный режим", "default"] translate = ["Режим переводчика", "translate"] get_ans = 0
Для смены режимов используем [слэш("/")+<имя_режима>], а все остальные команды примем как команды.
Реализуем это в Commander.py:
# Перечисления команд, режимов from command_enum import Command from mode_enum import Mode # Рабочие модули from translate.yandex_translate import Translator from weather import Weather from myanimelist import Myanimelist # Config from config import yandex_translate_api class Commander: def __init__(self): # Текущий, предыдущий режимы self.now_mode = Mode.default self.last_mode = Mode.default self.last_command = None # Для запомминания ответов пользователя self.last_ans = None # Работа с переводом self.translator = Translator(yandex_translate_api) def change_mode(self, to_mode): """ Меняет ре��им приема команд :param to_mode: Измененный мод """ self.last_mode = self.now_mode self.now_mode = to_mode self.last_ans = None def input(self, msg): """ Функция принимающая сообщения пользователя :param msg: Сообщение :return: Ответ пользователю, отправившему сообщение """ # Проверка на команду смены мода if msg.startswith("/"): for mode in Mode: if msg[1::] in mode.value: self.change_mode(mode) return "Режим изменен на " + self.now_mode.value[0] return "Неизвестный мод " + msg[1::] # Режим получения ответа if self.now_mode == Mode.get_ans: self.last_ans = msg self.now_mode = self.last_mode return "Ok!" if self.now_mode == Mode.default: # Погода if msg in Command.weather.value: return Weather.get_weather_today() # Топ аниме if msg in Command.anime_top.value: res = "" top = Myanimelist.get_top() for anime in top: res += anime + " : " + top[anime] + "\n" return res if self.now_mode == Mode.translate: if self.last_ans is None: # Если язык не выбран, просим пользователя ввести self.change_mode(Mode.get_ans) self.last_command = msg return "Выберите язык на который следует перевести" elif self.last_ans == "change": # Меняем переводимый язык self.last_ans = None self.change_mode(Mode.default) else: # Переводим return self.translator.translate_to(msg, self.last_ans) return "Команда не распознана!"
weather.py
import requests from bs4 import BeautifulSoup class Weather: @staticmethod def get_weather_today(city: str = "санкт-петербург") -> list: http = "https://sinoptik.com.ru/погода-" + city b = BeautifulSoup(requests.get(http).text, "html.parser") p3 = b.select('.temperature .p3') weather1 = p3[0].getText() p4 = b.select('.temperature .p4') weather2 = p4[0].getText() p5 = b.select('.temperature .p5') weather3 = p5[0].getText() p6 = b.select('.temperature .p6') weather4 = p6[0].getText() result = '' result = result + ('Утром :' + weather1 + ' ' + weather2) + '\n' result = result + ('Днём :' + weather3 + ' ' + weather4) + '\n' temp = b.select('.rSide .description') weather = temp[0].getText() result = result + weather.strip() return result
myanimelist.py
import requests import bs4 class Myanimelist: @staticmethod def get_top(count: int=5, by: str="") -> dict: types = ["", "airing", "upcoming", "tv", "movie", "ova", "special", "bypopularity", "favorite"] if by not in types: return {"error: ": "Неизвестный тип!"} html = requests.get("https://myanimelist.net/topanime.php?type="+by) soup = bs4.BeautifulSoup(html.text, "html.parser") res = {} for anime in soup.select(".ranking-list", limit=count): url = anime.select(".hoverinfo_trigger")[0]['href'] anime = anime.select(".hoverinfo_trigger")[0].findAll("img")[0] name = anime['alt'].split(":")[1].strip(" ") res[name] = url return res
yandex_translate.py
import requests from config import yandex_translate_api class Translator: """ Класс-переводчик использующий API Yandex Translate Параметры: _key -- ключ от API Yandex.Translate _yandex_comment -- согласовано с правилами офомления и использования API Yandex.Translate """ def __init__(self, key, comment=None): """ :param key: ключ от API Yandex.Translate :param comment: Комментарий к каждому переводу """ self._key = key if comment is None: self._yandex_comment = "\nПереведено сервисом «Яндекс.Переводчик» http://translate.yandex.ru/" else: self._yandex_comment = comment def translate(self, text, lang, to_lang=None): """ Переводит текст с указанного языка в другой указанный :param text: Текст, который нужно перевести :param lang: исходный язык :param to_lang: конечный язык :return: Переведенный текст """ if to_lang is not None: lang = f"{lang}-{to_lang}" main_url = "https://translate.yandex.net/api/v1.5/tr.json/translate" response = requests.get(f"{main_url}?" f"key={self._key}&" f"lang={lang}&" f"text={text}") return response.json()['text'][0] + self._yandex_comment def lang_identify(self, text, hint="ru,en"): """ Идентифицирует язык :param text: Текст :param hint: Подсказки для определения языка :return: код языка """ main_url = "https://translate.yandex.net/api/v1.5/tr.json/detect" response = requests.get(f"{main_url}?" f"key={self._key}&" f"hint={hint}&" f"text={text}") return response.json()['lang'] def translate_ru_en(self, text): """ Переводит текст с русского на английский :param text: Текст, который нужно перевести :return: Текст переведенный на английский язык """ if self.lang_identify(text) == "ru": to_lang = "en" from_lang = "ru" else: to_lang = "ru" from_lang = "en" return self.translate(text, from_lang, to_lang) def translate_to_ru(self, text, hint=None): """ Переводит текст на русский :param text: Текст, который нужно перевести :param hint: Подсказки к определению языка :return: Текст переведенный на русский язык """ if hint is None: hint = "ru,en" from_lang = self.lang_identify(text, hint) return self.translate(text, from_lang, "ru") def translate_to(self, text, to_lang, hint=None): """ Переводит текст в нужный язык :param text: Текст, который нужно перевести :param to_lang: Код результирующего языка :param hint: Подсказки к определению языка :return: Переведенный текст """ if hint is None: hint = "ru,en" from_lang = self.lang_identify(text, hint) return self.translate(text, from_lang, to_lang)
Весь код доступен на гитхабе: github.com/AppLoidx/VkLongPollBot
Добавляем клавиатуру:
Это очень легкий процесс, осложнения могут быть вызваны, когда мы будем изменять клавиатуру под определенную сигнатуру команд, которая отличается для каждого режима.
Чтобы добавить в окне диалогов клавиатуру, необходимо в методе messages.send указать параметр keyboard, принимающий json. Выглядит это следующим образом:
vk_api.messages.send(...,keyboard=keyboard_json,...)
Или же можно передать клавиатуру прямо с файла .json:
vk_api.messages.send(...,keyboard=open(filename,"r",encoding="UTF-8").read()
Документация: vk.com/dev/bots_docs_3?f=4.%2BКлавиатуры%2Bдля%2Bботов
Рассмотрим на примере нашей программы, добавив клавиатуру.
Для начала создадим файл keyboard.json:
{ "one_time": false, "buttons": [ [{ "action": { "type": "text", "label": "top anime" }, "color": "positive" }, { "action": { "type": "text", "label": "weather" }, "color": "positive" }], [{ "action": { "type": "text", "label": "translate" }, "color": "default" }] ] }
Чтобы убрать клавиатуры необходимо передать json с пустым buttons:
{"buttons":[],"one_time":true}
Переопределим send_message в server.py:
def send_msg(self, send_id, message): """ Отправка сообщения через метод messages.send :param send_id: vk id пользователя, который получит сообщение :param message: содержимое отправляемого письма :return: None """ return self.vk_api.messages.send(peer_id=send_id, message=message, keyboard=open("keyboards/default.json", "r", encoding="UTF-8").read())
И также в методе start:
def start(self): for event in self.long_poll.listen(): # Слушаем сервер if event.type == VkBotEventType.MESSAGE_NEW: if event.object.from_id not in self.users: self.users[event.object.from_id] = Commander() # Пришло новое сообщение if event.type == VkBotEventType.MESSAGE_NEW: self.send_msg(event.object.peer_id, self.users[event.object.from_id].input(event.object.text))
В результате получим:

Последнее слово
Не стоит использовать голый листинг исходников представленных здесь, они использованы лишь для того чтобы вы лучше понимали что происходит под капотом. Разумеется, они все юзабельны и можно использовать их по частям.
Лично я использовал такого бота для группового ассистента, который умел:
- создавать очереди из участников группы, в том числе и множество команд редактировавших очередь, таких как добавление, удаление, создание и тд.
- рассылать всем участникам сообщения
- задавал вопросы (например, по языку Java)
- давал возможность создавать заявки на обмен мест и т.п.
Проект на гитхабе
Исходники представленные здесь
