package main import ( "encoding/json" "flag" "fmt" "log" "os" "strconv" "strings" "unicode" "git.fromouter.space/hamcha/tg" ) type config struct { Token string Bind string WebhookURL string WebhookPath string MaxRequestsPerMessage int Log bool } 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", "mlpcard.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 Ponyhead string MCM string } func webhook(update tg.APIUpdate) { // Handle inline queries (99% of the usage I hope) if update.Inline != nil && update.Inline.Query != "" { query := update.Inline.Query results, err := mlpapiSearch(query) if err != nil { fmt.Println(err) // DO SOMETHING return } if cfg.Log { log.Printf(" -> %d cards", 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: strconv.FormatInt(card.GUID, 10), PhotoURL: face, ThumbURL: face, Title: card.FullName, Caption: caption, Width: 672, Height: 936, ReplyMarkup: &tg.APIInlineKeyboardMarkup{ InlineKeyboard: [][]tg.APIInlineKeyboardButton{buttons}, }, } } err = api.AnswerInlineQuery(tg.InlineQueryResponse{ QueryID: update.Inline.QueryID, Results: photos, }) 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 { cards, err := mlpapiSearch(cardname) if err != nil { errlist = append(errlist, cardname) } else { if cfg.Log { log.Printf(" -> %d cards", len(cards.Data)) } for _, card := range cards.Data { face, _, _ := getCardEntry(card, 0) cardmedia = append(cardmedia, tg.APIInputMediaPhoto{ Type: "photo", Media: face, }) if card.Type == "Mane" { face, _, _ := getCardEntry(card, 1) cardmedia = append(cardmedia, tg.APIInputMediaPhoto{ Type: "photo", Media: face, }) } } } } 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 := mlpapiGetCardByID(parts[1]) facenum, _ := strconv.Atoi(parts[2]) if err == nil { face, caption, buttons := getCardEntry(card, facenum) api.EditMedia(tg.ClientEditMediaData{ InlineID: *update.Callback.InlineID, Media: tg.APIInputMediaPhoto{ Type: "photo", Media: face, }, }) api.EditCaption(tg.ClientEditCaptionData{ InlineID: *update.Callback.InlineID, Caption: caption, 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, flipped int) (string, string, []tg.APIInlineKeyboardButton) { cid := toCardID(card.AllIDs[0]) buttons := []tg.APIInlineKeyboardButton{ { Text: "Ponyhead", URL: "https://ponyhead.com/cards/" + cid, }, } captions := []string{} suffix := "" if card.Type == "Mane" { suffix = "a" num := 1 if flipped != 0 { suffix = "b" num = 0 } buttons = append(buttons, tg.APIInlineKeyboardButton{ Text: "🔄", CallbackData: fmt.Sprintf("FLIP,%s,%d", card.GUID, num), }) } face := fmt.Sprintf("https://mcg.zyg.ovh/images/cards/%s%s.webp", cid, suffix) return face, strings.Join(captions, " - "), buttons } func toCardID(id string) string { id = strings.ToLower(id) idx := strings.IndexFunc(id, unicode.IsLetter) num, set := id[:idx], id[idx:] return set + num }