introduction.

Lately, I have been spending more money on food delivery than usual. I would blame it mostly on my conscription service, where I have been for the past 9 months. Yes, we can use food delivery services that would bring food to the battalion; no one bats an eye. Because, without small perks like these, a software developer, forced to work for 170€, would go insane.

Of course, you could argue that I'm just a lazy soldier or a picky eater. But in my defense, the food here has been getting repetitive, and there's only so much overcooked meat or plain stew I can eat. Besides, one has to reward themselves for putting up with all the usual crap that comes with developing software in a military setting. I will probably talk more about my conscription service in another article because the experience here has been pretty cool, all things considered. Most of my resentment with my time here has come from the people working in the infantry, not understanding what software developers are doing. Thus, creating unnecessary conflicts.

I am writing this blog because I wish to share what I have created over the weekend, not to berate the Estonian military or my conscription service. As a vivid Wolt user, a food delivery service operating in Estonia, I have been tinkering with ideas to automate the endless "what to eat for lunch" problem. To sum it up quickly, I came up with a solution that would send me automatic messages about currently available deals in Wolt. This required me to find a messaging platform with an easy-to-use, straightforward chat API. After a bit of research, I stumbled across Telegram. Telegram offers a fantastic and entirely free bot API for developers, which you can read more about here. Anyway, I believe it's enough of chit-chat, and it's time to bring that idea alive.

the application itself.

I'll be using Spring Boot because I believe it's the best framework for creating web applications. It's elegant and allows us to write less code. Furthermore, I'll be using Kotlin for this project because I have wanted to try that out for ages now. Finally, below is the finished application, which I'll be showing to you.

Final project

examining Wolt requests.

To find information about my favorite restaurants in Wolt, I went to the "Discovery" page and opened the developer tools. If you would like to follow along, clear the network tab and press on the desired restaurant. I also used the filter to only see "Fetch/XSR" requests. After clicking on the restaurant, we will find a request called "menu?unit_prices=true&show_weighted_items=true".

Search restaurant in Wolt

On further examination, we see that there exists an "items" field, and it contains all the information we need to find the deals we are interested in. Also, each item object has a "type" field, which says whether it's a "default" or a "deal" menu item. For now, it's enough to start building the application.

Restaurant menu request

creating the application.

Firstly, we'll create a project at Spring Initializr with the following settings:

Project Language Language Spring Boot Packaging Java
Gradle Project Kotlin 3.0.0 (SNAPSHOT) Jar 11

Then let's start by adding data classes for the objects that we got from Wolt's restaurant response. Remember when we looked at the response, it contained different fields like "items" and "name". The "items" field also had various fields like "image", "basePrice" and "type".

    
    data class Restaurant(val name: String = "",
                          val items: List <RestaurantItem> = ArrayList()
    ) {
    }
    
  
    
      data class RestaurantItem(val name: String = "",
                            val baseprice: Int = 0,
                            val type: String = "",
                            val image: String = ""
      ) {
          fun getFormattedBasePrice(): Float = baseprice.toFloat() / 100
      }
    
  

After that, we need a way to make requests to the same Wolt API. Kotlin has different libraries to make HTTP requests, but we'll be using Fuel, which in their own words, is "The easiest HTTP networking library for Kotlin/Android." We also need a way to turn the responses into valid Kotlin objects. For that, we can use GSON. So let's add both of these dependencies to our Gradle build file.

    
      dependencies {
        implementation("org.springframework.boot:spring-boot-starter-web")
        implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
        implementation("org.jetbrains.kotlin:kotlin-reflect")
        implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
        implementation("com.github.kittinunf.fuel:fuel:2.3.1")
        implementation("com.google.code.gson:gson:2.9.0")
        testImplementation("org.springframework.boot:spring-boot-starter-test")
        annotationProcessor("org.springframework.boot:spring-boot-configuration-processor")
      }
    
  

Because my goal was to send notifications when an item with the type "deal" exists, we'll be looping over each element to find items with the corresponding type. Additionally, let's add some values to the "application.properties", which will hold information about the bot and additional information to create requests. Do not worry about "userAgentHeader" and other Telegram fields. We will get back to those later.

    
      class BotService(val properties: BotConfigProperties) {

        fun findDeals() {
            val restaurants = listOf("hesburger-mannerheimintie", "hesburger-kamppi")
            for (r in restaurants) {
                sendRestaurantDealNotifications(properties.woltUrl.replace("{RESTAURANT_NAME}", r))
            }
        }

        private fun sendRestaurantDealNotifications(restaurantUrl: String) {
            restaurantUrl
                .httpGet()
                .header(mapOf("user-agent" to properties.userAgentHeader))
                .responseObject(Restaurant.Deserializer()) { _, _, result ->
                    val (restaurant, err) = result
                    for (item in restaurant?.items!!) {
                        if (item.type == "deal") {
                            val message = "${item.name}: ${item.getFormattedBasePrice()}€"
                            sendNotificationToTelegramBot(item.image, message)
                        }
                    }
                }
        }

        private fun sendNotificationToTelegramBot(imageUrl: String, message: String) {
            val params = listOf("photo" to imageUrl, "chat_id" to properties.chatId, "caption" to message)
            Fuel.get("https://api.telegram.org/bot${properties.botId}/sendPhoto", params)
                .response()
          }
      }
    
  
    
      @ConfigurationProperties(prefix = "twnb")
      @ConstructorBinding
      data class BotConfigProperties(
          val chatId: String,
          val botId: String,
          val woltUrl: String,
          val userAgentHeader: String,
      )
    
  
    
      twnb.chatId=
      twnb.botId=
      twnb.woltUrl=https://restaurant-api.wolt.com/v4/venues/slug/{RESTAURANT_NAME}/menu?unit_prices=true&show_weighted_items=true
      twnb.userAgentHeader=
    
  

Lastly, create a @Scheduler method to make requests to Wolt within a fixed timeframe. @Scheduler accepts cron expressions or plain milliseconds.

    
      @Component
      class Scheduler(val service: BotService) {

          @Scheduled(fixedRate = 18000000)
          fun woltDealScheduler() {
              service.findDeals()
          }
      }
    
  

setting up the bot.

Let's finish the application by making a Telegram bot. Creating bots with the Telegram API is very easy. Alex Sarafian has written a great tutorial on creating a private Telegram chatbot. To keep this article somewhat concise, I'll skip the bot initialization part and will refer you to Sarafian's post. Telegram bots work very well out of the box. In the end, I only used the "/sendPhoto" GET endpoint for this project. Now fill the "application.properties" with the corresponding data and get the "userAgentHeader" from this website. That's it, you're done!

Find the source code at GitHub

legal notice.

The information brought in this article is for study purposes only and using it will be at your own risk. Although the information from the server is publicly available, some websites might not allow accessing their public data using code. Web scraping can generally be done without asking for permission from the data owner if it does not violate the website’s terms of service.