From 51211e641bf3713104fb69bf9a5a63a0c04f40d3 Mon Sep 17 00:00:00 2001 From: Luca Date: Mon, 6 Mar 2023 00:08:45 +0100 Subject: [PATCH] Extend 'order_placed' endpoint to handle different item categories --- internal/pretix/client.go | 24 +++++ internal/pretix/order_placed.go | 159 +++++++++++++++++++++++--------- internal/pretix/server.go | 7 -- 3 files changed, 137 insertions(+), 53 deletions(-) create mode 100644 internal/pretix/client.go diff --git a/internal/pretix/client.go b/internal/pretix/client.go new file mode 100644 index 0000000..5f3ad15 --- /dev/null +++ b/internal/pretix/client.go @@ -0,0 +1,24 @@ +package pretix + +import ( + "fmt" + "net/http" + "strings" +) + +func (s Server) buildURL(endpoint string, args ...any) string { + baseURL := strings.TrimSuffix(s.config.BaseURL, "/") + return fmt.Sprintf(endpoint, append([]any{baseURL}, args...)...) +} + +func (s Server) doGetRequest(url string) (*http.Response, error) { + req, err := http.NewRequest(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Accept", "application/json") + req.Header.Set("Authorization", fmt.Sprint("Token ", s.config.APIToken)) + + return s.client.Do(req) +} diff --git a/internal/pretix/order_placed.go b/internal/pretix/order_placed.go index 9a3c7e1..365f23b 100644 --- a/internal/pretix/order_placed.go +++ b/internal/pretix/order_placed.go @@ -3,6 +3,7 @@ package pretix import ( "encoding/json" "fmt" + "math/rand" "net/http" "strconv" "strings" @@ -15,8 +16,17 @@ const ( eventOrderPlaced = "pretix.event.order.placed" urlIndividualOrder = "%s/api/v1/organizers/%s/events/%s/orders/%s/" + urlItem = "%s/api/v1/organizers/%s/events/%s/items/%d/" + urlItemCategory = "%s/api/v1/organizers/%s/events/%s/categories/%d/" ) +var adverbs = []string{" Außerdem ", " Zusätzlich ", " Weiterhin ", " Des Weiteren "} + +type categoryBin struct { + NumFree uint + NumPaid uint +} + func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Content-Type") != "application/json" { writeStatus(w, http.StatusBadRequest) @@ -42,16 +52,7 @@ func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { return } - req, err := http.NewRequest(http.MethodGet, s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code), nil) - if err != nil { - writeStatus(w, http.StatusInternalServerError) - return - } - - req.Header.Set("Accept", "application/json") - req.Header.Set("Authorization", fmt.Sprint("Token ", s.config.APIToken)) - - resp, err := s.client.Do(req) + resp, err := s.doGetRequest(s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code)) if err != nil { writeStatus(w, http.StatusInternalServerError) return @@ -73,6 +74,7 @@ func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { Code string Datetime time.Time Positions []struct{ + Item uint Price string } Total string @@ -97,59 +99,80 @@ func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { } if rowsAffected > 0 && order.Datetime.Add(72 * time.Hour).After(time.Now()) { // pretix retries failed webhooks for up to three days - numFree := 0 - numPaid := 0 + categories := map[string]categoryBin{} + for _, position := range order.Positions { euros, cents, err := parsePrice(position.Price) if err != nil { continue } + name := s.fetchCategory(data.Organizer, data.Event, position.Item) + + category := categories[name] if euros > 0 || cents > 0 { - numPaid++ + category.NumPaid++ } else { - numFree++ + category.NumFree++ } + + categories[name] = category } - var verb, free, conjunction, paid, noun, total string - if numFree+numPaid == 1 { - verb = "wurde " - } else { - verb = "wurden " - } - if numFree == 1 { - free = "ein kostenloses " - } else if numFree > 1 { - free = fmt.Sprint(numFree, " kostenlose ") - } - if numPaid == 1 { - paid = "ein bezahltes " - } else if numPaid > 1 { - paid = fmt.Sprint(numPaid, " bezahlte ") - } - if numFree > 0 && numPaid > 0 { - conjunction = "und " - } - if numPaid == 1 || numPaid == 0 && numFree == 1 { // match number of adjective immediately before noun - noun = "Ticket " - } else { - noun = "Tickets " - } - if totalEuros, totalCents, err := parsePrice(order.Total); err == nil && (totalEuros > 0 || totalCents > 0) { - var adverb, fraction string - if numPaid > 1 { - adverb = "insgesamt " + var body strings.Builder + body.WriteString("\U0001f389") + + rng := rand.New(rand.NewSource(time.Now().Unix())) + + adverb := " Es " + for category, bin := range categories { + var verb, free, conjunction, paid, noun, preposition, quote, total string + if bin.NumFree+bin.NumPaid == 1 { + verb = "wurde " + } else { + verb = "wurden " } - if totalCents > 0 { - fraction = fmt.Sprintf(",%02d", totalCents) + if bin.NumFree == 1 { + free = "ein kostenloses " + } else if bin.NumFree > 1 { + free = fmt.Sprint(bin.NumFree, " kostenlose ") } - total = fmt.Sprint("für ", adverb, totalEuros, fraction, " Euro ") + if bin.NumPaid == 1 { + paid = "ein bezahltes " + } else if bin.NumPaid > 1 { + paid = fmt.Sprint(bin.NumPaid, " bezahlte ") + } + if bin.NumFree > 0 && bin.NumPaid > 0 { + conjunction = "und " + } + if bin.NumPaid == 1 || bin.NumPaid == 0 && bin.NumFree == 1 { // match number of adjective immediately before noun + noun = "Produkt " + } else { + noun = "Produkte " + } + if category != "" { + preposition = `aus der Kategorie "` + quote = `" ` + } + if totalEuros, totalCents, err := parsePrice(order.Total); err == nil && (totalEuros > 0 || totalCents > 0) { + var adverb, fraction string + if bin.NumPaid > 1 { + adverb = "insgesamt " + } + if totalCents > 0 { + fraction = fmt.Sprintf(",%02d", totalCents) + } + total = fmt.Sprint("für ", adverb, totalEuros, fraction, " Euro ") + } + + body.WriteString(fmt.Sprint(adverb, verb, free, conjunction, paid, noun, preposition, category, quote, total, "bestellt.")) + + adverb = adverbs[rng.Intn(len(adverbs))] } message := event.MessageEventContent{ MsgType: event.MsgText, - Body: fmt.Sprint("\U0001f389 Es ", verb, free, conjunction, paid, noun, total, "bestellt."), + Body: body.String(), } success := s.matrix.Broadcast(&message) @@ -159,6 +182,50 @@ func (s Server) orderPlaced(w http.ResponseWriter, r *http.Request) { } } +func (s Server) fetchCategory(organizer, event string, item uint) string { + resp, err := s.doGetRequest(s.buildURL(urlItem, organizer, event, item)) + if err != nil { + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != 200 || resp.Header.Get("Content-Type") != "application/json" { + return "" + } + + decoder := json.NewDecoder(resp.Body) + itemData := struct{ + Category uint + }{} + + err = decoder.Decode(&itemData) + if err != nil || itemData.Category == 0 { + return "" + } + + resp, err = s.doGetRequest(s.buildURL(urlItemCategory, organizer, event, itemData.Category)) + if err != nil { + return "" + } + defer resp.Body.Close() + + if resp.StatusCode != 200 || resp.Header.Get("Content-Type") != "application/json" { + return "" + } + + decoder = json.NewDecoder(resp.Body) + itemCategory := struct{ + Name string + }{} + + err = decoder.Decode(&itemCategory) + if err != nil { + return "" + } + + return itemCategory.Name +} + func parsePrice(price string) (euros uint64, cents uint64, err error) { parts := strings.Split(price, ".") diff --git a/internal/pretix/server.go b/internal/pretix/server.go index 0ae68cf..e5a37c3 100644 --- a/internal/pretix/server.go +++ b/internal/pretix/server.go @@ -3,11 +3,9 @@ package pretix import ( "context" "database/sql" - "fmt" "io" "log" "net/http" - "strings" "sync" "git.luj0ga.de/franconian/matrix" @@ -96,11 +94,6 @@ func (s Server) createTables() error { return nil } -func (s Server) buildURL(endpoint string, args ...any) string { - baseURL := strings.TrimSuffix(s.config.BaseURL, "/") - return fmt.Sprintf(endpoint, append([]any{baseURL}, args...)...) -} - func writeStatus(w http.ResponseWriter, code int) { w.WriteHeader(code) io.WriteString(w, http.StatusText(code))