commit c155adcbe0f1e2adacd589da6c3f88d7ac674eef Author: Hamcha Date: Tue Aug 20 14:36:26 2019 +0200 Check in diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..904e860 --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.fromouter.space/mcg/mlpcardbot + +go 1.12 + +require git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3054bd3 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea h1:2VrIQZEHaUGjf9Rqsli+S+/ZReLpodPWTklVUqQPCX0= +git.fromouter.space/hamcha/tg v0.0.0-20181213132350-d7698a3c50ea/go.mod h1:j0xF3DL3Y+HWAMzaFQlJxl9bLOKmFNEZNemg19vC+Cc= diff --git a/main.go b/main.go new file mode 100644 index 0000000..12bb6e0 --- /dev/null +++ b/main.go @@ -0,0 +1,236 @@ +package main + +import ( + "encoding/json" + "flag" + "fmt" + "os" + "strconv" + "strings" + "unicode" + + "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", "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 { + query := update.Inline.Query + results, err := mlpapiSearch(query) + if err != nil { + fmt.Println(err) + // DO SOMETHING + return + } + + 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 { + 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: "http://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 +} diff --git a/mlpapi1.go b/mlpapi1.go new file mode 100644 index 0000000..4b24d25 --- /dev/null +++ b/mlpapi1.go @@ -0,0 +1,56 @@ +package main + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "time" +) + +type CardSearchResults struct { + Data []CardData `json:"data"` +} + +type CardSearchResultsSingle struct { + Data CardData `json:"data"` +} + +type CardData struct { + AllIDs []string `json:"allids"` + GUID int64 `json:"card_version_guid"` + FullName string `json:"fullname"` + Set string `json:"set"` + Type string `json:"type"` +} + +var netClient = &http.Client{ + Timeout: time.Second * 20, +} + +func mlpapiSearch(query string) (results CardSearchResults, err error) { + query = url.QueryEscape(query) + requrl := fmt.Sprintf("https://www.ferrictorus.com/mlpapi1/cards?query=%s", query) + response, err := netClient.Get(requrl) + if err != nil { + return + } + defer response.Body.Close() + + err = json.NewDecoder(response.Body).Decode(&results) + return +} + +func mlpapiGetCardByID(id string) (card CardData, err error) { + requrl := fmt.Sprintf("https://www.ferrictorus.com/mlpapi1/cards/%s", id) + response, err := netClient.Get(requrl) + if err != nil { + return + } + defer response.Body.Close() + + var res CardSearchResultsSingle + err = json.NewDecoder(response.Body).Decode(&res) + card = res.Data + return +}