Привет! Наверное, каждый из нас когда-то сталкивался с ситуацией, когда нужно срочно куда-то уехать, но все Ж/Д билеты уже раскуплены. В этой статье я расскажу о том, как я писал Telegram бота для отслеживания и покупки освободившихся билетов Укрзалізниці.


Как это работает


Для покупки железнодорожных билетов в Украине компания Укрзалізниця запустила ресурс http://booking.uz.gov.ua/. Ресурс удобен тем, что не нужно посещать кассы, чтобы забрать сам билет. Достаточно показать проводнику QR код с посадочного талона на экране смартфона либо распечатав на принтере.


Проблема состоит в том, что на популярные рейсы места очень быстро заканчиваются и иногда купить билет довольно проблематично. Однако, многие люди не покупают билет, а бронируют его. Бронь действует лишь 24 часа и после этого, если она не выкуплена в кассе, билет возвращается в пул свободных. Таким образом, необходимо успеть словить этот момент, когда билет доступен для покупки до того, как его снова забронируют или купят.


Было принято решение решить эту задачу с помощью скрипта, который раз в минуту проверяет свободные билеты на интересующий поезд и, в случае наличия, резервирует его на 15 минут. После чего пользователю необходимо завершить процедуру оплаты через веб браузер.


В качестве интерфейса был выбран Telegram так как это новая платформа для меня и я хотел с ней немного разобраться. В качестве бонуса сразу получаем уведомления на мобильный, не задумываясь о push нотификациях или email'ах.
В качестве языка программирования был выбран Python.


Интерфейс


И всё же, как это работает с точки зрения пользователя?
Бот распознает следующие команды:


  • /help — вернёт список поддерживаемых команд
  • /trains 2016-06-12 Kyiv Lviv — вернёт список поездов из Киева во Львов, отправляющихся 12 июня 2016 года
  • /scan Ivanov Ivan 2016-06-12 Kyiv Lviv 743K — запустит мониторинг билетов на поезд 743К Киев-Львов. Возвращает ID данного сканирования
  • /status_1234 — вернет состояние сканирования с ID 1234
  • /abort_1234 — остановит сканирование с ID 1234

В случае успешного резервирования билета пользователь получит сообщение, содержащее Session ID. Этот ID затем необходимо вручную прописать в cookie браузера и завершить покупку билета.


UZ API


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


В API используются только POST запросы. Для защиты от использования API сторонними разработчиками почти во всех вызовах в тело включается токен. Без токена можно производить только поиск станций.


Стоит также отметить, некоторые нюансы работы с датами. Во-первых, формат даты меняется в зависимости от текущей локали API. Например, для локали en формат будет mm.dd.yyyy. Тогда как для ua и ru это будет привычный нам dd.mm.yyyy. Во-вторых, для некоторых запросов дата представляется в виде timestamp, однако он зависит от состояния летнего/зимнего времени. Потому я решил не заморачиваться с сериализацией/десериализацией данных штампов, а использовать их в том виде, в котором API возвращает их.


Получение токена


Покопавшись в подключаемых сайтом скриптах, можно с легкостью обнаружить такой кусок:


var ajax = $v.ajax(url).header({
    'GV-Ajax': 1,
    'GV-Referer': encodeURI(GV.site.htcur_url + GV.site.requestUri),
    'GV-Screen': screen.width + 'x' + screen.height,
    'GV-Token': localStorage.getItem('gv-token') || ''
});

Здесь мы видим, что при вызовах в API токен считывается из localStorage браузера. Осталось найти где он туда записывается.


Эта часть была самой интересной, потому как простым поиском по html и js этого найти не удалось. Потратив несколько часов в гугле, я наткнулся на статью, в которой автор решает такой же вопрос с мониторингом билетов на сайте УЗ. Итак, в статье подробно описано, что токен генерируется обфусцированным с помощью JJEncode кодом. За несколько минут находим реализацию деобфускатора на питоне, который и будет использоваться в дальнейшем.


Краткий API reference


Для вызова методов API, необходимо включать следующие заголовки:


GV-Ajax: 1
GV-Referer: http://booking.uz.gov.ua/en/
GV-Token: <token>

Поиск станций


Например, для формирования подсказок автодополнения станций выполняется запрос с пустым телом по адресу http://booking.uz.gov.ua/en/purchase/station/ky/, где ky — это то, что пользователь вводит в текстовое поле выбора станции.


В ответ сервер отправляет примерно такой JSON:


{
  "value": [
    {
      "title": "Kyiv",
      "station_id": "2200001"
    },
    {
      "title": "Kyivska Rusanivka",
      "station_id": "2201180"
    },
    {
      "title": "Kyj",
      "station_id": "2031278"
    },
    {
      "title": "Kykshor",
      "station_id": "2011189"
    }
  ],
  "error": null,
  "data": {
    "req_text": [
      "ky",
      "лн"
    ]
  },
  "captcha": null
}

Поиск поездов


Для поиска поездов необходимо выполнить запрос на http://booking.uz.gov.ua/en/purchase/search/ с таким телом:


station_id_from=2200001  # ID станции отправления
station_id_till=2218000  # ID станции назначения
date_dep=06.12.2016      # дата отправления в формате mm.dd.yyyy
time_dep=00:00 
time_dep_till= 
another_ec=0 
search=

В ответ мы получим список поездов, следующих по указанному маршруту. Так же, в ответ будет включена информация о количестве свободных мест в вагонах каждого типа (Люкс, Купе, Плацкарт, и т. д.):


{
  "value": [
    {
      "num": "743Л",
      "model": 1,
      "category": 1,
      "travel_time": "5:01",
      "from": {
        "station_id": 2200001,
        "station": "Darnytsya",
        "date": 1465741200,
        "src_date": "2016-06-12 17:20:00"
      },
      "till": {
        "station_id": 2218000,
        "station": "Lviv",
        "date": 1465759260,
        "src_date": "2016-06-12 22:21:00"
      },
      "types": [
        {
          "title": "Seating first class",
          "letter": "С1",
          "places": 117
        },
        {
          "title": "Seating second class",
          "letter": "С2",
          "places": 176
        }
      ],
      "reserve_error": "reserve_24h"
    },
    {
      "num": "091К",
      "model": 0,
      "category": 0,
      "travel_time": "7:25",
      "from": {
        "station_id": 2200001,
        "station": "Kyiv-Pasazhyrsky",
        "date": 1465760460,
        "src_date": "2016-06-12 22:41:00"
      },
      "till": {
        "station_id": 2218000,
        "station": "Lviv",
        "date": 1465787160,
        "src_date": "2016-06-13 06:06:00"
      },
      "types": [
        {
          "title": "Suite / first-class sleeper",
          "letter": "Л",
          "places": 11
        },
        {
          "title": "Coupe / coach with compartments",
          "letter": "К",
          "places": 50
        }
      ],
      "reserve_error": "reserve_24h"
    }
  ],
  "error": null,
  "data": null,
  "captcha": null
}

Просмотр вагонов


Просмотреть список вагонов и количество свободных мест можно выполнив запрос на http://booking.uz.gov.ua/en/purchase/coaches/ с таким телом:


station_id_from=2200001
station_id_till=2218000
date_dep=1462976400
train=743К               # номер поезда
model=3                  # модель поезда
coach_type=С2            # тип вагона (люкс, купе, и т. д.)
round_trip=0
another_ec=0

В ответ мы получим список вагонов данного типа с количеством свободных мест и ценой:


{
  "coach_type_id": 10,
  "coaches": [
    {
      "num": 1,
      "type": "С",
      "allow_bonus": false,
      "places_cnt": 21,
      "has_bedding": false,
      "reserve_price": 1700,
      "services": [],
      "prices": {
        "А": 35831
      },
      "coach_type_id": 10,
      "coach_class": "2"
    },
    {
      "num": 3,
      "type": "С",
      "allow_bonus": false,
      "places_cnt": 21,
      "has_bedding": false,
      "reserve_price": 1700,
      "services": [],
      "prices": {
        "А": 35831
      },
      "coach_type_id": 9,
      "coach_class": "2"
    }
  ],
  "places_allowed": 8,
  "places_max": 8
}

Просмотр свободных мест


Для просмотра свободных мест в выбранном вагоне необходимо выполнить запрос на http://booking.uz.gov.ua/en/purchase/coach/ с телом:


station_id_from=2200001
station_id_till=2218000
train=743К
coach_num=1
coach_class=2
coach_type_id=19
date_dep=1462976400
change_scheme=1

В ответ получаем список свободных мест:


{
  "value": {
    "places": {
      "А": [
        "8",
        "12",
        "16",
        "18",
        "22",
        "27",
        "28",
        "32",
        "33",
        "34",
        "36",
        "37",
        "38",
        "39",
        "42",
        "43",
        "47",
        "48",
        "49",
        "55",
        "56"
      ]
    }
  },
  "error": null,
  "data": null,
  "captcha": null
}

Работа с корзиной


Для того, чтобы положить билет в корзину, тем самым зарезервировав его на 15 минут для оплаты, необходимо выполнить запрос на http://booking.uz.gov.ua/en/cart/add/ с телом:


code_station_from:2200007
code_station_to:2218000
train:743К
date:1463580000
round_trip:0
places[0][ord]:0
places[0][coach_num]:5
places[0][coach_class]:2
places[0][coach_type_id]:22
places[0][place_num]:37
places[0][firstname]:Name
places[0][lastname]:Surname
places[0][bedding]:0
places[0][child]:
places[0][stud]:
places[0][transp]:0
places[0][reserve]:0

Мониторинг


Итак, вот мы и добрались до самой интересной части, до мониторинга свободных билетов. Для решения этой задачи был реализован класс UZScanner, который имеет несколько методов:


  • добавить поезд для мониторинга
  • удалить поезд из мониторинга
  • запуск мониторинга
  • остановка мониторинга

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


Мониторинг является асинхронным процессом и выполняется как корутина. В случае успешного резервирования билета, мониторинг выполняет callback, информируя пользователя о результате. Для этого в конструктор класса передается callback-функция.


class UZScanner(object):

    def __init__(self, success_cb, delay=60):
        self.success_cb = success_cb

        self.loop = asyncio.get_event_loop()
        self.delay = delay
        self.session = aiohttp.ClientSession()
        self.client = UZClient(self.session)
        self.__state = dict()
        self.__running = False

Для того, чтобы вызывающий код различал для какого именно пользователя произошел callback, помимо данных о самом поезде также передаётся callback ID:


def add_item(self, success_cb_id, firstname, lastname, date,
             source, destination, train_num, ct_letter=None):
    scan_id = uuid4().hex
    self.__state[scan_id] = dict(
        success_cb_id=success_cb_id,
        firstname=firstname,
        lastname=lastname,
        date=date,
        source=source,
        destination=destination,
        train_num=train_num,
        ct_letter=ct_letter,
        lock=asyncio.Lock(),
        attempts=0,
        error=None)
    return scan_id

Основная функция мониторинга является циклом, в котором для каждого поезда запускается функция проверки наличия мест.


async def run(self):
    self.__running = True
    while self.__running:
        for scan_id, data in self.__state.items():
            asyncio.ensure_future(self.scan(scan_id, data))
        await reliable_async_sleep(self.delay)

Сама же функция мониторинга работает по такому алгоритму:


  • Получить список поездов на заданную дату по заданному маршруту
  • Проверить, есть ли нужный поезд
  • Для всех вагонов (либо только для указанного типа) проверить наличие мест
  • Попробовать зарезервировать первое найденное свободное место
  • В случае успеха, выполнить callback, удалить поезд из мониторинга

async def scan(self, scan_id, data):
    if data['lock'].locked():
        return

    async with data['lock']:
        data['attempts'] += 1

        train = await self.client.fetch_train(
            data['date'], data['source'], data['destination'], data['train_num'])
        if train is None:
            return self.handle_error(
                scan_id, data, 'Train {} not found'.format(data['train_num']))

        if data['ct_letter']:
            coach_type = self.find_coach_type(train, data['ct_letter'])
            if coach_type is None:
                return self.handle_error(
                    scan_id, data, 'Coach type {} not found'.format(data['ct_letter']))
            coach_types = [coach_type]
        else:
            coach_types = train.coach_types

        session_id = await self.book(train, coach_types, data['firstname'], data['lastname'])
        if session_id is None:
            return self.handle_error(scan_id, data, 'No available seats')

        await self.success_cb(data['success_cb_id'], session_id)
        self.abort(scan_id)

@staticmethod
async def book(train, coach_types, firstname, lastname):
    with UZClient() as client:
        for coach_type in coach_types:
            for coach in await client.list_coaches(train, coach_type):
                try:
                    seats = await client.list_seats(train, coach)
                except ResponseError:
                    continue
                for seat in seats:
                    try:
                        await client.book_seat(train, coach, seat, firstname, lastname)
                    except ResponseError:
                        continue
                    return client.get_session_id()

Заключение


Мы разобрались с API, используемым порталом http://booking.uz.gov.ua и реализовали скрипт резервирования билета. Код доступен на GitHub. Docker image доступен на DockerHub. Также доступен Telegram бот @uz_ticket_bot


UPD УЗ заблокировала IP бота. Бот временно отключён.

Поделиться с друзьями
-->

Комментарии (52)


  1. oxidmod
    13.06.2016 23:21
    +2

    имхо, так можно сервис и заДДОСить, тем более что он до сих пор в тестовой експлуатации


    1. ketom_z80
      14.06.2016 08:16
      +1

      Заддосить можно все, что угодно. Но это же не повод не реализовывать программы, выполняющие полезную функцию, правда?
      А шильдик «тестовая эксплуатация» там висит с момента запуска сервиса. Лет 5 уже как.


      1. oxidmod
        14.06.2016 08:21
        +3

        одно дело писать прораммы использующие публичное АПИ, но на сколько я знаю разработчики не открывали его. Да, они не особо то и скрывали, но обфускация важного кода какбы намекает, что АПИ не публичное. Сервис банально может быть не расчитан на нагрузки созданные всеми желающими и в итоге ни Вы не купити билетик, ни сотни других покупателей


        1. extempl
          14.06.2016 19:14

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


  1. Sergey_datex
    13.06.2016 23:30
    +3

    Автор, отличная работа. Вы материализовали мысль многих, кто пользуется этим сервисом


    1. opanas
      14.06.2016 00:57
      +2

      Я пользуюсь бесплатным сервисом watchmyticket.com (donate по желанию), но здорово теперь иметь еще один вариант.


      1. Youw
        14.06.2016 08:11

        Сейчас: www.watchmyticket.com — 503 Over Quota
        Хабраэффект?


    1. ketom_z80
      14.06.2016 08:16

      Спасибо. Рад, что кому-то кроме меня это может быть пригодиться.


  1. almaz010891
    14.06.2016 00:36
    +2

    Отличная идея! Спасибо, прочитал доку по боту, там есть следующее: /scan Firstname Lastname 2016-01-01 Kyiv Lviv 743K C2
    Что такое C2 — Как я понял, с помощью этого можно указать тип места (плацкарт/купе и т.д.) Какие типы кроме C2 могут быть тогда, можно более развернуто?


    1. ketom_z80
      14.06.2016 08:19

      Все верно, С2 — это тип вагона. /trains возвращает список поездов в таком виде:


      Train: 013Ш
      Departure time: 2016-07-01 01:08:00
      Travel time: 8:52
      ~~~~~~~~~~
      Л: 2 (Suite / first-class sleeper)
      К: 10 (Coupe / coach with compartments)
      П: 16 (Berth / third-class sleeper)
      ==========
      
      Train: 744Л
      Departure time: 2016-07-01 06:20:00
      Travel time: 4:59
      ~~~~~~~~~~
      С1: 164 (Seating first class)
      С2: 403 (Seating second class)
      ==========

      Здесь видно, что типы вагонов бывают С1, С2, Л, К, П


      1. almaz010891
        14.06.2016 21:39

        Спасибо за пояснения!


  1. PEgorov
    14.06.2016 01:20
    +9

    У Укрзализныци наверняка есть такой же неочевидный лайфхак, как и у РЖД, суть его в следующем: если очень-очень нужно уехать на конкретном поезде, а билетов на него в продаже нет, то можно поискать билеты с «фиктивными» пересадками. Для тех, кто знаком с понятием split ticketing все уже понятно, для остальных поясню на актуальных на момент написания комментария данных.

    Допустим, есть поезд 062А, который едет из Москвы в Питер, проезжая при этом несколько промежуточных станций. Билетов на этот поезд на сегодня уже в продаже нет, но если очень хочется уехать, то можно купить два билета на одно и то же место, на два последовательных сегмента маршрута. Например, можно купить на 37 место 5 вагона из Москвы до Бологое, и на это же место от Бологое до Питера. Полного билета, из Москвы в Питер, на это место в продаже нет. Я не уверен, почему это так, скорее всего это как-то связано с квотами станций.

    А еще этот милый лайфхак иногда позволяет сэкономить приличную сумму денег. Например, прямо сейчас билет на сегодня из Москвы в Питер, в купе поезда 082А стоит 4326 рублей. Но вместо одного билета можно купить два, от Москвы до Окуловки за 1451 рублей и от Окуловки до Питера, за 947. В сумме это обойдется Вам в 2398 рублей, экономия равна 1928 рублям или 45%, при этом качественно разницы никакой нет совершенно.


    1. ketom_z80
      14.06.2016 08:22
      +1

      Вероятно, такая штука есть. Однако, в Украине помимо обычных поездов запустили быстрые электропоезда "интерсити". Зачастую они следуют без остановок по всему маршруту. Лично я чаще всего пользуюсь именно такими поездами, потому ваш вариант здесь вряд ли сработает.


      1. sergey_prokofiev
        14.06.2016 13:34
        -2

        Все поезда интерсити на украине идут с промежуточными остановками. В этом можно легко убедиться здесь: intercity.kiev.ua/raspisanie


    1. rootes
      14.06.2016 10:02
      +1

      Да, фиктивные «пересадки» не раз выручали. Однозначно стоит добавить в реализацию бота.
      Правда у нас в украинских поездах цена билетов наоборот получается дороже процентов на 30%, вероятно из-за двойной оплаты страховки и дополнительных сборов.
      Еще есть прикол, когда за неделю уже билетов на поезд нет, а в день отправления «магическим» образом появляется куча свободных мест в разных вагонах. Но это уже другая история, о куче посредников, их бизнесе и времени бронирования.


      1. oxidmod
        14.06.2016 10:12
        +3

        УкрЗалізниця бронирует в каждом поезде определенный процент мест для своих сотрудников (они имеют право раз или два в год взять бесплатные билеты туда и обратно на любой поезд), для сотрудников разнообразных органов (внезапные командировки и так далее). Естественно эта бронь не выкупается в полном объеме никогда и в последний день снимается.


        1. demimurych
          14.06.2016 13:08

          бронь снимается дважды.
          первый раз ровно за 12 часов до отправления поезда.
          второй раз по разному — для некоторых маршрутов за 3 часа для некоторых за полтора. Так и не понял в каких случаях выбирается какое время. (возможно проходящий или прямой)
          Этот алгоритм работает даже в сезон на курортных маршрутах. Ни раз так брал билеты.


      1. lysk1959
        14.06.2016 15:24

        Да не на 30% а вдвое. Пример? Интерсити Днепр-Харьков, промежуточная Лозовая.
        Днепр-Харьков =133 грн, Днепр-Лозовая=113грн, Лозовая-Харьков 112 грн, итого 225 против 133,
        грабеж чистой воды.


    1. Alibobaevich
      14.06.2016 20:25

      В УЗ на каждую станцию выделяется определенный пул билетов, и часто такое бывает, что на больших станцииях билеты расходятся быстрее чем на мелких. Например если из Запорожья в Киев нет билетов ни на один поезд, то почти всегда из соседнего Мелитополя чуть ли не десяток в каждом вагоне. Выходит чуть дороже, но зато точно доедешь куда нужно и без нервотрепок по поводу наличия или отсутсвия снятой брони в последний день перед отправкой. Ну или если в обратную сторону — взять билет с посадкой из соседнего городка и добраться туда автобусом или машиной.


    1. madkite
      15.06.2016 20:37

      У Укрзализныци с этим как раз наоборот. Они при ажиотаже предпочитают продавать билеты от конечной до конечной, видимо зарабатывая при этом больше денег (билет стоит дороже, а на оставшуюся часть пути найти пассажира сложнее). Потому иногда система не позволяет купить билет с промежуточной станции, при этом позволяя купить с начальной до конечной. Но тут есть другая проблема — если пассажир не сел в поезд, то на одной из следующих станций они могут как-то подать эту информацию и билет снова поступает в продажу. Я не знаю как оно технически работает, это мне рассказывали в справочной. Потому чтобы не попасть в неприятную ситуацию, если вы садитесь на промежуточной станции, нужно посылать телеграмму начальнику поезда о том, что посадка будет производиться на такой-то станции.


  1. Chpock
    14.06.2016 04:46

    Как-то одноразово понадобилось отловить билет на УЗ, в итоге «на коленке» родился такой tcl скрипт с проверкой билетов на направление/дату (через альтернативный api) и отправкой sms (через сервис twilio) — http://paste.tclers.tk/3842 Билет был успешно пойман и скрипт отправился в архив.


  1. brainm
    14.06.2016 10:11
    -2

    А что за баннер у вас при входе на сайт открывается?


    1. ketom_z80
      14.06.2016 10:26
      +1

      О каком сайте/баннере речь?


  1. OtherF
    14.06.2016 10:26

    Телеграм бот не работает. (
    Он не отвечает на команды, даже на /help никакой реакции.


    1. ketom_z80
      14.06.2016 10:26
      +3

      UPD УЗ заблокировала IP бота. Бот временно отключён.


  1. Tribunal
    14.06.2016 10:30

    Когда-то тоже писал бот на Python. Поработал пару недель, и они добавили этот токен. Побороть его тогда не смог (.
    Вещь отличная, особенно когда пользуется ей сравнительно немного людей.


  1. lysk1959
    14.06.2016 13:35

    На каком-то из сайтов — посредников (а билетами УЗ торгует не только booking, но и многие другие, сайты дирекций например, Приват-банк тот же) уже реализованы функции ожидания нужных билетов. В системе резервирования УЗ есть еще такая особенность, что в продажу выставляется не весь поезд одновременно, а сначала несколько вагонов, потом по их заполнению до определенного процента — добавляется еще вагон и т.д. Поэтому имеем ситуацию, что вчера например в поезде нет рядом 4х мест для семейной поездки, а сегодня они уже есть… И да, квоты между железными дорогами… Это просто пир духа… С моей станции ЮЖД например нет билета на нужный поезд, но с соседней станции ПриднЖД — есть… Приходится брать, благо разница в цене копеечная, но его надо обязательно предварительно за день регистрировать у дежурного по вокзалу, чтобы изменить место посадки, иначе билет аннулируется…


    1. Alibobaevich
      14.06.2016 20:30

      Десятки раз брал билет от соседнего города и никогда не регистрировал свой билет у дежурного по вокзалу — без проблем садился на своей станции(правда всегда брал в пределах одной ЖД)


  1. ArisChik
    14.06.2016 13:35

    Интересно, а внутренняя система, которой пользуются кассиры, смотрит на ту же БД\сервер? Я к тому, что вы только что открыли ящик Пандоры — оказывается легкое шаманство и можно тупо «положить» сервер, а если он еще и один с тем что кассиры используют…


  1. BoDVa
    14.06.2016 13:52
    +1

    Есть, давненько уже, запущенный и работающий бот
    https://telegram.me/railwaybot
    На днях там запустилась еще и продажа билетов, к сожалению не на прямую через букинг, но тоже интересный вариант.


    1. ketom_z80
      14.06.2016 16:04

      Благодарю за данный экземпляр!


  1. BoDVa
    14.06.2016 13:56

    Вопрос по авторской реализации. Для чего реализовано добавление в корзину, ведь корзина не привязывается к аккаунту и не получится просто зайти и купить, ожидающий билет. Это сделано просто для резервирования?


    1. ketom_z80
      14.06.2016 16:00

      Корзина привязана к session id. Подменив этот id в браузере, можно произвести оплату билета. Это не идеальное решение, но первое, что пришло в голову.


  1. IgorPastukhov
    14.06.2016 14:21

    Конечно, такие вещи давно многими реализованы «для себя». Просто не афишируются, ибо приведет к появлению captcha, как и у РЖД. Но использование telegramm безусловно интересный вариант, спасибо!


  1. vpoplavskii
    14.06.2016 15:19

    Запустил с Docker. Телеграм бот работает, отвечает на команду /help, но при вводе /trains или /scan возвращает 403 страницу с УЗ.


    1. ketom_z80
      14.06.2016 15:59

      Очевидно, немного забомбили запросами и УЗ оперативно добавили еще одну проверку.


    1. ketom_z80
      14.06.2016 20:39

      Попробуйте еще раз. Добавлена поддержка браузерных юзер агентов.


      1. vpoplavskii
        15.06.2016 09:25

        Спасибо, работает. А можно добавить бронь 2-3х мест рядом с исключением туалетов?


        1. ketom_z80
          15.06.2016 09:50

          Не думаю, что у меня будет возможность сделать это в ближайшее время. Но я с удовольствием просмотрю ваш PR.


  1. VladKopanev
    14.06.2016 18:48

    В целом не плохо, тоже в голову приходили мысли, а как же это автоматизировать. Но смущает только один момент: «вручную прописать в cookie браузера и завершить покупку билета.» — как-то не юзер френдли получается, я понимаю, что тут ограничения среды вступают в силу,, но все же.


    1. extempl
      14.06.2016 19:19

      Это решается простеньким js кодом упакованным в URL и запускаемым на странице УЗ. Достаточно туда впихнуть промпт который будет спрашивать sessionID и записывать его в LS.


      1. ketom_z80
        14.06.2016 20:35

        PR приветствуется!


        1. extempl
          14.06.2016 21:36

          Done. Немного коряво вышло из-за ограничения гитхаба на публикацию букмарклетов в .md, так что нужно смерджить в бранч gh-pages где будет открываться html с линкой букмарклета.


          1. extempl
            14.06.2016 21:47

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


            1. ketom_z80
              14.06.2016 22:21

              Увы, но данный способ не подходит, так как куки будет записана для домена github.io и не будет работать для booking.uz.gov.ua.


              1. extempl
                15.06.2016 06:25

                Так запускать-то его надо именно на странице booking.uz.gov.ua. Я в ридми описал. Нужно не просто линку нажать на странице, а добавить её как закладку. Оттого оно и зовётся букмарклетом.


              1. extempl
                15.06.2016 06:33
                +1

                Для наглядности скринкаст как пользоваться: http://screencast.com/t/xsqsLiE2


                1. ketom_z80
                  15.06.2016 09:51

                  Спасибо за разъяснение.


  1. almaz010891
    14.06.2016 21:43

    Бот сейчас лежит судя по-всему. Не отзывается ни на единую команду…


    1. ketom_z80
      14.06.2016 22:13

      Все верно. Там по этому поводу есть UPD в конце статьи.


  1. teddygod
    15.06.2016 09:51

    Интересный подход, сыроват пока что, но тем немение для УЗ будет новым витком развития


  1. wowkin
    21.06.2016 14:49

    SCAN_DELAY_SEC лучше ставить минимум 60 секунд.
    Информация на сервере все равно обновляется только каждые 20 минут.
    Поэтому лишний раз DDoS-ить сервер не надо.