package main import ( "encoding/json" "flag" "fmt" "os" "strconv" "strings" "git.fromouter.space/hamcha/tg" ) type Config struct { Token string Bind string WebhookURL string WebhookPath string MaxRequestsPerMessage int } func checkErr(err error, msg string, args ...interface{}) { if err != nil { fmt.Printf("FATAL ERROR\n"+msg+":\n ", args...) fmt.Println(err.Error()) os.Exit(1) } } var api *tg.Telegram var cfg Config func main() { cfgpath := flag.String("config", "stappa.conf", "Path to config file") flag.Parse() cfgfile, err := os.Open(*cfgpath) checkErr(err, "Could not open config file") err = json.NewDecoder(cfgfile).Decode(&cfg) checkErr(err, "Could not decode JSON from config file contents") cfgfile.Close() // Set default maxreq if cfg.MaxRequestsPerMessage < 1 { cfg.MaxRequestsPerMessage = 5 } api = tg.MakeAPIClient(cfg.Token) api.SetWebhook(cfg.WebhookURL) api.HandleWebhook(cfg.Bind, cfg.WebhookPath, webhook) } type CardFaceFlipPics struct { Normal string Flipped string Scryfall string Edhrec string MCM string } func webhook(update tg.APIUpdate) { // Handle inline queries (99% of the usage I hope) if update.Inline != nil { query := update.Inline.Query offset, _ := strconv.Atoi(update.Inline.Offset) results, err := scryfallSearch(query, offset) if err != nil { fmt.Println(err) // DO SOMETHING return } nextcard := "" if results.HasMore { nextcard = strconv.Itoa(offset + len(results.Data)) } photos := make([]tg.APIInlineQueryResultPhoto, len(results.Data)) for i, card := range results.Data { face, caption, buttons := getCardEntry(card, 0) photos[i] = tg.APIInlineQueryResultPhoto{ Type: "photo", ResultID: card.ID, PhotoURL: face.Large, ThumbURL: face.Normal, Title: card.Name, Caption: caption, Width: 672, Height: 936, ReplyMarkup: &tg.APIInlineKeyboardMarkup{ InlineKeyboard: [][]tg.APIInlineKeyboardButton{buttons}, }, } } err = api.AnswerInlineQuery(tg.InlineQueryResponse{ QueryID: update.Inline.QueryID, Results: photos, NextOffset: nextcard, }) if err != nil { fmt.Println(err) // DO SOMETHING return } } // Check for card requests if update.Message != nil && update.Message.Text != nil { requests := getCardRequests(*update.Message.Text) if len(requests) > cfg.MaxRequestsPerMessage { api.SendTextMessage(tg.ClientTextMessageData{ ChatID: update.Message.Chat.ChatID, Text: fmt.Sprintf("You asked for way too many cards (%d!), please only ask me for at most %d cards in a single message.", len(requests), cfg.MaxRequestsPerMessage), ReplyID: &update.Message.MessageID, }) return } cardmedia := []tg.APIInputMediaPhoto{} errlist := []string{} for _, cardname := range requests { card, err := scryfallGetCardByName(cardname) if err != nil { errlist = append(errlist, cardname) } else { if card.ImageUris.Large == "" && card.CardFaces != nil { for _, cardface := range card.CardFaces { cardmedia = append(cardmedia, tg.APIInputMediaPhoto{ Type: "photo", Media: cardface.ImageUris.Large, }) } } else { cardmedia = append(cardmedia, tg.APIInputMediaPhoto{ Type: "photo", Media: card.ImageUris.Large, }) } } } if len(cardmedia) > 0 { api.SendAlbum(tg.ClientAlbumData{ ChatID: update.Message.Chat.ChatID, Media: cardmedia, Silent: true, ReplyID: &update.Message.MessageID, }) } if len(errlist) > 0 { api.SendTextMessage(tg.ClientTextMessageData{ ChatID: update.Message.Chat.ChatID, Text: "I couldn't find these cards you mentioned: " + strings.Join(errlist, ", "), ReplyID: &update.Message.MessageID, }) } } // Handle inline callback requests (flipped cards) if update.Callback != nil { if update.Callback.Data != nil && strings.HasPrefix(*update.Callback.Data, "FLIP") { parts := strings.SplitN(*update.Callback.Data, ",", 3) card, err := scryfallGetCardByID(parts[1]) facenum, _ := strconv.Atoi(parts[2]) if err == nil { face, _, buttons := getCardEntry(card, facenum) api.EditMedia(tg.ClientEditMediaData{ ChatID: update.Message.Chat.ChatID, MessageID: update.Message.MessageID, Media: tg.APIInputMediaPhoto{ Type: "photo", Media: face.Large, }, ReplyMarkup: &tg.APIInlineKeyboardMarkup{ InlineKeyboard: [][]tg.APIInlineKeyboardButton{buttons}, }, }) } } else { fmt.Println("Unknown callback: ", *update.Callback.Data) } api.AnswerCallback(tg.ClientCallbackQueryData{ QueryID: update.Callback.ID, }) } } func getCardRequests(str string) (out []string) { remaining := str for len(remaining) > 1 { nextToken := strings.Index(remaining, "[[") if nextToken < 0 { break } endToken := strings.Index(remaining[nextToken:], "]]") if endToken < 0 { break } out = append(out, remaining[nextToken+2:nextToken+endToken]) remaining = remaining[nextToken+2+endToken:] } return } func getCardEntry(card CardData, facenum int) (CardImage, string, []tg.APIInlineKeyboardButton) { buttons := []tg.APIInlineKeyboardButton{ { Text: "Scryfall", URL: card.ScryfallURI, }, } if card.RelatedUris.Edhrec != "" { buttons = append(buttons, tg.APIInlineKeyboardButton{ Text: "EDHREC", URL: card.RelatedUris.Edhrec, }) } if card.PurchaseUris.Cardmarket != "" { buttons = append(buttons, tg.APIInlineKeyboardButton{ Text: "MCM", URL: card.PurchaseUris.Cardmarket, }) } captions := []string{} if card.EdhrecRank != nil { captions = append(captions, fmt.Sprintf("EDHREC rank: #%d", *card.EdhrecRank)) } if card.Eur != "" { captions = append(captions, fmt.Sprintf("cardmarket: € %s", card.Eur)) } face := card.ImageUris if card.ImageUris.Large == "" && card.CardFaces != nil { face = card.CardFaces[facenum].ImageUris num := 1 if facenum != 0 { num = 0 } buttons = append(buttons, tg.APIInlineKeyboardButton{ Text: "🔄", CallbackData: fmt.Sprintf("FLIP,%s,%d", card.ID, num), }) } return face, strings.Join(captions, " - "), buttons }