263 lines
5.7 KiB
Go
263 lines
5.7 KiB
Go
package pretix
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"maunium.net/go/mautrix/event"
|
|
)
|
|
|
|
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 {
|
|
Cents uint64
|
|
Euros uint64
|
|
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)
|
|
return
|
|
}
|
|
|
|
data := struct{
|
|
Action string
|
|
Code string
|
|
Event string
|
|
Organizer string
|
|
}{}
|
|
decoder := json.NewDecoder(r.Body)
|
|
|
|
err := decoder.Decode(&data)
|
|
if err != nil {
|
|
writeStatus(w, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if data.Action != eventOrderPlaced {
|
|
writeStatus(w, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
resp, err := s.doGetRequest(s.buildURL(urlIndividualOrder, data.Organizer, data.Event, data.Code))
|
|
if err != nil {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != 200 {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if resp.Header.Get("Content-Type") != "application/json" {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
decoder = json.NewDecoder(resp.Body)
|
|
order := struct{
|
|
Code string
|
|
Datetime time.Time
|
|
Positions []struct{
|
|
Item uint
|
|
Price string
|
|
}
|
|
}{}
|
|
|
|
err = decoder.Decode(&order)
|
|
if err != nil {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
result, err := s.db.Exec("INSERT INTO orders VALUES(?);", order.Code)
|
|
if err != nil {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
if rowsAffected > 0 && order.Datetime.Add(72 * time.Hour).After(time.Now()) { // pretix retries failed webhooks for up to three days
|
|
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 {
|
|
category.Cents += cents
|
|
if carry := category.Cents / 100; carry > 0 {
|
|
category.Cents -= carry * 100
|
|
category.Euros += carry
|
|
}
|
|
category.Euros += euros
|
|
category.NumPaid++
|
|
} else {
|
|
category.NumFree++
|
|
}
|
|
|
|
categories[name] = category
|
|
}
|
|
|
|
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 bin.NumFree == 1 {
|
|
free = "ein kostenloses "
|
|
} else if bin.NumFree > 1 {
|
|
free = fmt.Sprint(bin.NumFree, " kostenlose ")
|
|
}
|
|
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 bin.Euros > 0 || bin.Cents > 0 {
|
|
var adverb, fraction string
|
|
if bin.NumPaid > 1 {
|
|
adverb = "insgesamt "
|
|
}
|
|
if bin.Cents > 0 {
|
|
fraction = fmt.Sprintf(",%02d", bin.Cents)
|
|
}
|
|
total = fmt.Sprint("für ", adverb, bin.Euros, 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: body.String(),
|
|
}
|
|
|
|
success := s.matrix.Broadcast(&message)
|
|
if !success {
|
|
writeStatus(w, http.StatusInternalServerError)
|
|
}
|
|
}
|
|
}
|
|
|
|
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{
|
|
InternalName string `json:"internal_name"`
|
|
}{}
|
|
|
|
err = decoder.Decode(&itemCategory)
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
|
|
return itemCategory.InternalName
|
|
}
|
|
|
|
func parsePrice(price string) (euros uint64, cents uint64, err error) {
|
|
parts := strings.Split(price, ".")
|
|
|
|
euros, err = strconv.ParseUint(parts[0], 10, 64)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
|
|
if len(parts) > 1 {
|
|
for len(parts[1]) < 2 {
|
|
parts[1] += "0"
|
|
}
|
|
parts[1] = parts[1][:2]
|
|
|
|
parts[1] = strings.TrimLeft(parts[1], "0")
|
|
if parts[1] == "" {
|
|
parts[1] = "0"
|
|
}
|
|
|
|
cents, err = strconv.ParseUint(parts[1], 10, 64)
|
|
if err != nil {
|
|
return 0, 0, err
|
|
}
|
|
}
|
|
|
|
return euros, cents, nil
|
|
}
|