Weather Bot goes live

I re-wrote my weather bot completely in Kotlin and split the functionality into three parts:

  1. The back-end downloads RSS feeds and compares them against the user database, it sends the alerts to user’s mobiles
  2. The front-end adds users and handles verification
  3. The front-end administration panel allows for addition, deletion of users.

Both the front-ends are written using jQuery, Bootstrap, vanilla Javascript and Kotlin/ktor. They are both single page web apps and allow for all functions without page reload.

Here is the sign up page on my website:

screencapture-opens3-net-main-html-2019-01-22-07_59_04

Here is the admin interface:

alerts

The database functions are handled using data classes and SQL objects using a SQL DSL for Kotlin caused Exposed.

Below is an example of the object classes, data classes and a function required to return a list of user objects. This code is used to return a list of users to the admin interface.

The Kotlin SQL DSL makes the programming of very complicated data structures a breeze.

object users : Table() {
    val id = integer("id").autoIncrement().primaryKey() // Column
    val name = varchar("name", length = 150) // Column
    val country = varchar("country", length = 150) // Column
    val state = varchar("state", length = 150) // Column
    val mobile = varchar("mobile", length = 50) // Column
    val opt1 = varchar("opt1", length = 50).nullable() // Column
    val opt2 = varchar("opt2", length = 50).nullable() // Column

}

object userkeywords : Table() {
    val id = integer("id").autoIncrement().primaryKey() // Column
    val uuid = integer("uuid").nullable()
    val keywords = varchar("keywords", length = 150) // Column
}

data class completeUser(
    val id: Int,
    val name: String,
    val country: String,
    val state: String,
    val mobile: String,
    val keywords: MutableList,
    val opt1: String,
    val opt2: String
)
fun getAllUsers(): MutableList {
    connectToDB()
    var returnedUsers = mutableListOf()
    transaction {
        SchemaUtils.create(users, userkeywords)
        val allKeywords = userkeywords.selectAll()
        for (user in users.selectAll()) {
            val thisUser = completeUser(
                id = user[users.id],
                name = user[users.name],
                country = user[users.country],
                state = user[users.state],
                mobile = user[users.mobile],
                opt1 = "NIL",
                opt2 = "NIL",
                keywords = mutableListOf()
            )
            println("${user[users.id]}: ${user[users.name]}")
            for (k in allKeywords) {
                if (k[userkeywords.uuid] == user[users.id]) {
                    val kw = k[userkeywords.keywords]
                    thisUser.keywords.add(kw)
                }
            }
            returnedUsers.add(thisUser)
        }

    }
    return returnedUsers
}

To send this data in JSON it is very easy.

First “install” the GSON feature:

install(ContentNegotiation) {
        gson {
            setDateFormat(DateFormat.LONG)
            setPrettyPrinting()
        }
    }

Then add the route:

get("/getUsers") {
    var theseUsers = getAllUsers()
    call.respond(theseUsers)
}

Pretty braindead stuff really. Handling queries in JavaScript is also very easy (the following function adds all retrieved users to a drop down menu):

var getUserList = function() {
  $.ajax({
    type: "GET",
    url: "/getUsers",
    // The key needs to match your method's input parameter (case-sensitive).
    contentType: "application/json; charset=utf-8",
    dataType: "json",
    success: function(data) {
      $('#deleteUser').empty();
      data.forEach(function(element) {
        $('#deleteUser').append(`
${element.name} mobile: ${element.mobile}`);
      });
    },
    failure: function(errMsg) {
      alert(errMsg);
    }
  });
};

As for the back-end, I had to write the API in Kotlin to connect to the Telstra API. I had to use khttp because ktor client can not set custom headers at the moment.

Here is an example of how it works:

//provision a number
val response = khttp.post(
      url = "https://tapi.telstra.com/v2/messages/provisioning/subscriptions",
      headers = mapOf("content-type" to "application/json","authorization" to "Bearer ${token}" , "cache-control" to "no-cache"),
            data = "{ \"activeDays\": 30 }")

// get/update API token
val payload = mapOf(
      "grant_type" to "client_credentials",
      "scope" to "NSMS",
      "client_id" to "xxx",
      "client_secret" to "xxx")

val response = khttp.post(
      url = " https://tapi.telstra.com/v2/oauth/token",
      headers = mapOf("Content-Type" to "application/x-www-form-urlencoded"),
      data = payload)

// send a message
val response = khttp.post(
      url = " https://tapi.telstra.com/v2/messages/sms",                  
      headers = mapOf("content-type" to "application/json",
      "authorization" to "Bearer ${token}"),
      data = ourJson.toString())
 

I had to setup a dummy route in ktor and use WireShark to sniff the packets to figure out which headers needed to be set before I could finally figure out how the system worked.

Thanks to Telstra for the free 1000 messages per month and thanks to the folks at Jetbrains for all their awesome libraries and products.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s