changeset 21:08faa7039be7

Add infrastructure for callback handling
author Lewin Bormann <lbo@spheniscida.de>
date Sat, 10 Dec 2016 09:37:36 +0100
parents 851d4eaa877e
children 9e7757101e75
files api_schema.go handler_todo.go handlers.go http.go pull.go status.go webhook.go
diffstat 7 files changed, 102 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/api_schema.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/api_schema.go	Sat Dec 10 09:37:36 2016 +0100
@@ -27,38 +27,29 @@
 	Forward_From_Chat chat
 }
 
-type inlineQuery struct {
+type callbackQuery struct {
 	ID   string
 	From user
-	// Location
-	Query  string
-	Offset string
-}
-
-type callbackQuery struct {
-	ID   string
-	From string
 	// Message that this callback originated from
 	Message message
 	// Callback data
 	Data string
 }
 
+type update struct {
+	Update_ID      uint64
+	Message        message
+	Channel_Post   message
+	Callback_Query callbackQuery
+}
+
+// Reply from getUpdates
 type updateReply struct {
 	OK     bool
 	Result []update
 }
 
-type update struct {
-	Update_ID           uint64
-	Message             message
-	Edited_Message      message
-	Channel_Post        message
-	Edited_Channel_Post message
-	Inline_Query        inlineQuery
-	Callback_Query      callbackQuery
-}
-
+// Reply from getWebhookInfo
 type webhookInfoReply struct {
 	OK     bool
 	Result webhookInfo
@@ -71,6 +62,7 @@
 	Last_Error_Message   string
 }
 
+// Body type for sendMessage and webhook reply
 type sendMessage struct {
 	Chat_ID             uint64               `json:"chat_id"`
 	Text                string               `json:"text"`
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/handler_todo.go	Sat Dec 10 09:37:36 2016 +0100
@@ -0,0 +1,16 @@
+package main
+
+func todoButtonTest(msg message) (replyContent, error) {
+	buttonRows := [][]inlineKeyboardButton{}
+
+	for _, b := range []string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15"} {
+		buttonRows = append(buttonRows, []inlineKeyboardButton{inlineKeyboardButton{Callback_Data: b, Text: b}})
+	}
+
+	return replyContent{text: "Please select", buttons: inlineKeyboardMarkup{buttonRows}}, nil
+}
+
+func todoDoneHandler(token string) error {
+
+	return nil
+}
--- a/handlers.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/handlers.go	Sat Dec 10 09:37:36 2016 +0100
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	"errors"
 	"fmt"
 	"log"
 	"strings"
@@ -16,6 +17,8 @@
 	statusCmd   = "status"
 	todoCmd     = "todo"
 	todoTestCmd = "todo-"
+
+	todoDoneCallback = "markdone"
 )
 
 type handler struct {
@@ -23,6 +26,9 @@
 	desc string
 }
 
+// A handler taking the callback data. `data` only contains the token, not the callback type.
+type callbackHandler (func(data string) error)
+
 var (
 	handlers = map[string]handler{
 		echoCmd:     {echoHandler, "Anfrage zurücksenden"},
@@ -33,6 +39,10 @@
 		todoCmd:     {missingHandler, "Aufgabenliste"},
 		todoTestCmd: {todoButtonTest, "TODO test (internal)"},
 	}
+
+	callbackHandlers = map[string]callbackHandler{
+		todoDoneCallback: todoDoneHandler,
+	}
 )
 
 type replyContent struct {
@@ -70,17 +80,48 @@
 	return replyContent{text: srvStatus.String()}, nil
 }
 
-func todoButtonTest(msg message) (replyContent, error) {
-	buttonRows := [][]inlineKeyboardButton{}
+func dispatch(upd update) (sendMessage, error) {
+	if upd.Message.Message_ID > 0 {
+		srvStatus.commands++
+
+		msg, err := dispatchMessage(upd.Message)
+
+		if err != nil {
+			srvStatus.errors++
+		}
 
-	for _, b := range []string{"1", "2", "3"} {
-		buttonRows = append(buttonRows, []inlineKeyboardButton{inlineKeyboardButton{Callback_Data: b, Text: b}})
+		return msg, err
+	} else if upd.Callback_Query.ID != "" {
+		srvStatus.callbacks++
+		dispatchCallback(upd.Callback_Query)
+
+		return sendMessage{}, nil
+	} else {
+
+		reply := sendMessage{
+			Chat_ID:    upd.Message.Chat.ID,
+			Parse_Mode: "Markdown",
+			Text:       "",
+		}
+
+		return reply, nil
 	}
-
-	return replyContent{text: "Please select", buttons: inlineKeyboardMarkup{buttonRows}}, nil
 }
 
-func dispatch(msg message) (sendMessage, error) {
+// Dispatches an incoming callbackQuery. The data field has the format callback_type:token.
+func dispatchCallback(cbq callbackQuery) error {
+	parts := strings.Split(cbq.Data, ":") // callback:token
+
+	if handler, ok := callbackHandlers[parts[0]]; ok {
+		handler(parts[1])
+		return nil
+	} else {
+		log.Println("Didn't find callback handler for type", parts[0])
+		return errors.New("handler not found")
+	}
+}
+
+func dispatchMessage(msg message) (sendMessage, error) {
 	var rp replyContent
 	var err error
 
--- a/http.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/http.go	Sat Dec 10 09:37:36 2016 +0100
@@ -54,9 +54,9 @@
 	debugChat := chat{ID: 1, Type: "private", Title: "__debug", First_Name: "debug"}
 	msg := message{Chat: debugChat, From: debugUser, Message_ID: 1, Date: uint64(time.Now().Unix()), Text: string(body)}
 
-	srvStatus.numCommands++
+	srvStatus.commands++
 
-	reply, err := dispatch(msg)
+	reply, err := dispatch(update{Message: msg, Update_ID: 12345})
 
 	if err != nil {
 		srvStatus.errors++
--- a/pull.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/pull.go	Sat Dec 10 09:37:36 2016 +0100
@@ -45,13 +45,15 @@
 	}
 
 	for u := range upds.Result {
-		srvStatus.numCommands++
-
-		sM, err := dispatch(upds.Result[u].Message)
+		sM, err := dispatch(upds.Result[u])
 
 		if err != nil {
 			log.Println(err)
-			srvStatus.errors++
+		}
+
+		// Empty reply, e.g. for callback handlers
+		if sM.Chat_ID == 0 {
+			continue
 		}
 
 		err = sendReply(sM)
@@ -79,12 +81,16 @@
 
 	url := buildURL(sendMessageMethod)
 
+	srvStatus.apiCalls++
+
 	rp, err := defaultClient.Post(url, jsonBodyType, bytes.NewBuffer(body))
 
 	if err != nil {
+		srvStatus.apiErrors++
 		log.Println(err)
 		return err
 	} else if rp.StatusCode != http.StatusOK {
+		srvStatus.apiErrors++
 		log.Println("Error:", rp.StatusCode, rp.Status)
 	}
 
--- a/status.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/status.go	Sat Dec 10 09:37:36 2016 +0100
@@ -9,13 +9,15 @@
 	ok          bool
 	dbConnected bool
 	database    string
-	numCommands uint
+	commands    uint
+	callbacks   uint
 	errors      uint
 	apiErrors   uint
+	apiCalls    uint
 }
 
 func (ss serverStatus) String() string {
 	webhookInfo, _ := getWebhookInfo()
-	return fmt.Sprintf("ok=%t db=%t dbname=%s cmds=%d errs=%d api-errs=%d %s",
-		ss.ok, ss.dbConnected, ss.database, ss.numCommands, ss.errors, ss.apiErrors, webhookInfo)
+	return fmt.Sprintf("ok=%t db=%t dbname=%s cmds=%d callbacks=%d errs=%d api-errs=%d api-calls=%d %s",
+		ss.ok, ss.dbConnected, ss.database, ss.commands, ss.callbacks, ss.errors, ss.apiErrors, ss.apiCalls, webhookInfo)
 }
--- a/webhook.go	Fri Dec 09 22:35:19 2016 +0100
+++ b/webhook.go	Sat Dec 10 09:37:36 2016 +0100
@@ -52,6 +52,8 @@
 }
 
 func getWebhookInfo() (string, error) {
+	srvStatus.apiCalls++
+
 	rp, err := defaultClient.Get(buildURL(getWebhookInfoMethod))
 
 	if err != nil {
@@ -76,6 +78,7 @@
 
 		return fmt.Sprintf("pending=%d last-error='%s'", info.Pending_Update_Count, info.Last_Error_Message), nil
 	} else {
+		srvStatus.apiErrors++
 		log.Println("Couldn't getWebhookInfo:", rp.StatusCode)
 		return "", errors.New("Bad response code")
 	}
@@ -109,7 +112,7 @@
 		return
 	}
 
-	responseMsg, err := dispatch(upd.Message)
+	responseMsg, err := dispatch(upd)
 
 	if err != nil {
 		rp.WriteHeader(http.StatusInternalServerError)
@@ -117,6 +120,12 @@
 		return
 	}
 
+	// Empty response, e.g. for callback handlers
+	if responseMsg.Chat_ID == 0 {
+		rp.WriteHeader(http.StatusOK)
+		return
+	}
+
 	responseMsg.Method = sendMessageMethod
 
 	responseBody, err := json.Marshal(responseMsg)