func (r *RentalBotClient) Unreserve(username string, tool string, day string) (ok bool, result keybase1.KVGetResult, err error) {
	var reservations map[string]string
	result, err = r.Lookup(tool)
	if err != nil {
		return false, result, err  
	}
	var entryValue string
	if result.EntryValue != nil {
		entryValue = *result.EntryValue
	}
	reservations = make(map[string]string)
	if err = json.Unmarshal([]byte(entryValue), &reservations); err != nil {
		return false, result, err
	}
	reserver, ok := reservations[day]
	if !ok {
		return true, result, nil
	} else if reserver != username {
		return false, result, nil
	}
	expectedRevision := result.Revision + 1
	delete(reservations, day)
	bytes, err := json.Marshal(reservations)
	if err != nil {
		return false, result, err
	}
	_, err = r.api.PutEntryWithRevision(&r.team, r.namespace, tool, string(bytes), expectedRevision)
	if err != nil {
		if e, ok := err.(kbchat.Error); !ok || e.Code != kbchat.RevisionErrorCode {
			return false, result, err
		}
		result, err = r.Lookup(tool)
		if err != nil {
			return false, result, err  
		}
		return false, result, nil  
	}
	return true, result, nil  
}
Unreserve a tool for a given day if that day is currently reserved by the given user.
Note: if you unreserve a not-added or deleted tool, it will not add the tool.
Returns (whether action is successful, most recent get result if applicable, error)
package main
import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"os"
	"strings"
	"github.com/keybase/go-keybase-chat-bot/kbchat"
	"github.com/keybase/go-keybase-chat-bot/kbchat/types/keybase1"
	"golang.org/x/sync/errgroup"
)
type SecretKeyKVStoreAPI struct {
	api     kbchat.KVStoreAPI
	secrets map[string](map[string][]byte)
	config  SecretKeyKVStoreAPIConfig
}
type SecretKeyKVStoreAPIConfig struct {
	secretName            string
	plaintextEntryKeyName string
}
func NewSecretKeyKVStoreAPI(api kbchat.KVStoreAPI) *SecretKeyKVStoreAPI {
	config := SecretKeyKVStoreAPIConfig{
		secretName:            "_secret",
		plaintextEntryKeyName: "_key",
	}
	sc := SecretKeyKVStoreAPI{
		api:     api,
		secrets: make(map[string](map[string][]byte)),
		config:  config,
	}
	return &sc
}
func (sc *SecretKeyKVStoreAPI) loadSecret(teamName string, namespace string) ([]byte, error) {
	if _, ok := sc.secrets[teamName]; !ok {
		sc.secrets[teamName] = make(map[string][]byte)
	}
	if secret, ok := sc.secrets[teamName][namespace]; ok {
		return secret, nil
	}
	newSecret := make([]byte, sha256.BlockSize)
	if _, err := rand.Read(newSecret); err != nil {
		return nil, err
	}
	_, err := sc.api.PutEntryWithRevision(&teamName, namespace, sc.config.secretName, hex.EncodeToString(newSecret), 1)
	if err != nil {
		if e, ok := err.(kbchat.Error); !ok || e.Code != kbchat.RevisionErrorCode {
			return nil, err
		}
		res, err := sc.api.GetEntry(&teamName, namespace, sc.config.secretName)
		if err != nil {
			return nil, err
		}
		var entryValue string
		if res.EntryValue != nil {
			entryValue = *res.EntryValue
		}
		existingSecret, err := hex.DecodeString(entryValue)
		if err != nil {
			return nil, err
		}
		sc.secrets[teamName][namespace] = existingSecret
	} else {
		sc.secrets[teamName][namespace] = newSecret
	}
	return sc.secrets[teamName][namespace], nil
}
func (sc *SecretKeyKVStoreAPI) obfuscateEntryKey(teamName string, namespace string, entryKey string) (string, error) {
	secret, err := sc.loadSecret(teamName, namespace)
	if err != nil {
		return "", err
	}
	mac := hmac.New(sha256.New, secret)
	mac.Write([]byte(entryKey))
	hmacEntryKey := mac.Sum(nil)
	return hex.EncodeToString(hmacEntryKey), nil
}
func (sc *SecretKeyKVStoreAPI) PutEntry(teamName *string, namespace string, entryKey string, entryValue string) (result keybase1.KVPutResult, err error) {
	return sc.PutEntryWithRevision(teamName, namespace, entryKey, entryValue, 0)
}
func (sc *SecretKeyKVStoreAPI) PutEntryWithRevision(teamName *string, namespace string, entryKey string, entryValue string, revision int) (result keybase1.KVPutResult, err error) {
	if teamName == nil {
		return result, fmt.Errorf("teamName must be defined for SecretKeyKVStoreAPI methods")
	}
	return sc.PutEntryWithRevisionAndTeam(*teamName, namespace, entryKey, entryValue, revision)
}
func (sc *SecretKeyKVStoreAPI) PutEntryWithRevisionAndTeam(teamName string, namespace string, entryKey string, entryValue string, revision int) (result keybase1.KVPutResult, err error) {
	var keyedValue map[string]string
	if err = json.Unmarshal([]byte(entryValue), &keyedValue); err != nil {
		return result, err
	}
	keyedValue[sc.config.plaintextEntryKeyName] = entryKey
	bytes, err := json.Marshal(keyedValue)
	if err != nil {
		return result, err
	}
	hmacEntryKey, err := sc.obfuscateEntryKey(teamName, namespace, entryKey)
	if err != nil {
		return result, err
	}
	result, err = sc.api.PutEntryWithRevision(&teamName, namespace, hmacEntryKey, string(bytes), revision)
	if err != nil {
		return result, err
	}
	result.EntryKey = entryKey
	return result, err
}
func (sc *SecretKeyKVStoreAPI) DeleteEntry(teamName *string, namespace string, entryKey string) (result keybase1.KVDeleteEntryResult, err error) {
	return sc.DeleteEntryWithRevision(teamName, namespace, entryKey, 0)
}
func (sc *SecretKeyKVStoreAPI) DeleteEntryWithRevision(teamName *string, namespace string, entryKey string, revision int) (result keybase1.KVDeleteEntryResult, err error) {
	if teamName == nil {
		return result, fmt.Errorf("teamName must be defined for SecretKeyKVStoreAPI methods")
	}
	return sc.DeleteEntryWithRevisionAndTeam(*teamName, namespace, entryKey, revision)
}
func (sc *SecretKeyKVStoreAPI) DeleteEntryWithRevisionAndTeam(teamName string, namespace string, entryKey string, revision int) (result keybase1.KVDeleteEntryResult, err error) {
	hmacEntryKey, err := sc.obfuscateEntryKey(teamName, namespace, entryKey)
	if err != nil {
		return result, err
	}
	result, err = sc.api.DeleteEntryWithRevision(&teamName, namespace, hmacEntryKey, revision)
	if err != nil {
		return result, err
	}
	result.EntryKey = entryKey
	return result, err
}
func (sc *SecretKeyKVStoreAPI) GetEntry(teamName *string, namespace string, entryKey string) (result keybase1.KVGetResult, err error) {
	if teamName == nil {
		return result, fmt.Errorf("teamName must be defined for SecretKeyKVStoreAPI methods")
	}
	return sc.GetEntryWithTeam(*teamName, namespace, entryKey)
}
func (sc *SecretKeyKVStoreAPI) GetEntryWithTeam(teamName string, namespace string, entryKey string) (result keybase1.KVGetResult, err error) {
	hmacEntryKey, err := sc.obfuscateEntryKey(teamName, namespace, entryKey)
	if err != nil {
		return result, err
	}
	result, err = sc.api.GetEntry(&teamName, namespace, hmacEntryKey)
	if err != nil {
		return result, err
	}
	result.EntryKey = entryKey
	return result, err
}
func (sc *SecretKeyKVStoreAPI) ListNamespaces(teamName *string) (keybase1.KVListNamespaceResult, error) {
	return sc.ListNamespaces(teamName)
}
func (sc *SecretKeyKVStoreAPI) ListEntryKeys(teamName *string, namespace string) (result keybase1.KVListEntryResult, err error) {
	keys, err := sc.api.ListEntryKeys(teamName, namespace)
	if err != nil {
		return result, err
	}
	tmp := keys.EntryKeys[:0]
	for _, e := range keys.EntryKeys {
		if strings.HasPrefix(e.EntryKey, "_") {
			continue
		}
		get, err := sc.api.GetEntry(teamName, namespace, e.EntryKey)
		if err != nil {
			return result, err
		}
		var entryValue string
		if get.EntryValue != nil {
			entryValue = *get.EntryValue
		}
		var keyedValue map[string]string
		if err := json.Unmarshal([]byte(entryValue), &keyedValue); err != nil {
			return result, err
		}
		e.EntryKey = keyedValue[sc.config.plaintextEntryKeyName]
		tmp = append(tmp, e)
	}
	keys.EntryKeys = tmp
	return keys, nil
}
type RentalBotClient struct {
	api       kbchat.KVStoreAPI
	team      string
	namespace string
}
func NewRentalBotClient(api kbchat.KVStoreAPI, teamName string, namespace string) *RentalBotClient {
	r := RentalBotClient{api: api, team: teamName, namespace: namespace}
	return &r
}
func (r *RentalBotClient) Lookup(tool string) (keybase1.KVGetResult, error) {
	return r.api.GetEntry(&r.team, r.namespace, tool)
}
func (r *RentalBotClient) Add(tool string) (ok bool, result keybase1.KVGetResult, err error) {
	result, err = r.Lookup(tool)
	if err != nil {
		return false, result, err  
	} else if result.EntryValue != nil {
		return true, result, nil  
	}
	expectedRevision := result.Revision + 1
	val := make(map[string]string)
	bytes, err := json.Marshal(val)
	if err != nil {
		return false, result, err
	}
	_, err = r.api.PutEntryWithRevision(&r.team, r.namespace, tool, string(bytes), expectedRevision)
	if err != nil {
		if e, ok := err.(kbchat.Error); !ok || e.Code != kbchat.RevisionErrorCode {
			return false, result, err
		}
		result, err := r.Lookup(tool)
		if err != nil {
			return false, result, err  
		}
		return false, result, nil  
	}
	return true, result, nil  
}
func (r *RentalBotClient) Remove(tool string) (ok bool, result keybase1.KVGetResult, err error) {
	result, err = r.Lookup(tool)
	if err != nil {
		return false, result, err  
	} else if result.EntryValue == nil {
		return true, result, nil  
	}
	expectedRevision := result.Revision + 1
	_, err = r.api.DeleteEntryWithRevision(&r.team, r.namespace, tool, expectedRevision)
	switch err.(type) {
	case nil:
		return true, result, nil
	case kbchat.Error:
		result, err := r.Lookup(tool)
		if err != nil {
			return false, result, err  
		}
		return false, result, nil  
	default:
		return false, result, err
	}
}
func (r *RentalBotClient) Reserve(username string, tool string, day string) (ok bool, result keybase1.KVGetResult, err error) {
	var reservations map[string]string
	result, err = r.Lookup(tool)
	if err != nil {
		return false, result, err  
	}
	var entryValue string
	if result.EntryValue != nil {
		entryValue = *result.EntryValue
	}
	reservations = make(map[string]string)
	if err = json.Unmarshal([]byte(entryValue), &reservations); err != nil {
		return false, result, err
	}
	if _, ok := reservations[day]; ok {
		return false, result, nil  
	}
	reservations[day] = username
	expectedRevision := result.Revision + 1
	bytes, err := json.Marshal(reservations)
	if err != nil {
		return false, result, err
	}
	_, err = r.api.PutEntryWithRevision(&r.team, r.namespace, tool, string(bytes), expectedRevision)
	if err != nil {
		if e, ok := err.(kbchat.Error); !ok || e.Code != kbchat.RevisionErrorCode {
			return false, result, err
		}
		result, err := r.Lookup(tool)
		if err != nil {
			return false, result, err  
		}
		return false, result, nil  
	}
	return true, result, nil  
}