From ab4dd0dde97befd588e2fd616e3d4a6000ba9203 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?V=C3=ADctor=20Cuadrado=20Juan?= Date: Thu, 21 Feb 2019 12:05:00 +0100 Subject: [PATCH] Add mtgcard service This service makes API calls to https://scryfall.com/docs/api/ and returns information about "Magic: the Gathering" cards. It provides a command: !mtcard card name And it provides expansion for: [[card name]] Both will return an html message with card information, links, and price. I'm curious if you folks see it relevant to be added to go-neb or not. If you want to see it live, hop to #magicthegathering:matrix.org (currently running non-dedicated). --- README.md | 1 + src/github.com/matrix-org/go-neb/goneb.go | 1 + .../go-neb/services/mtgcard/mtgcard.go | 168 ++++++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 src/github.com/matrix-org/go-neb/services/mtgcard/mtgcard.go diff --git a/README.md b/README.md index 387d2a9..b1f2252 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ List of Services: - [JIRA](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/jira/) - Integration with JIRA - [RSS Bot](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/rssbot/) - An Atom/RSS feed reader - [Travis CI](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/travisci/) - Receive build notifications from Travis CI + - [Mtgcard](https://matrix-org.github.io/go-neb/pkg/github.com/matrix-org/go-neb/services/mtgcard/) - Query information about Magic: the Gathering card game ## Configuring Realms diff --git a/src/github.com/matrix-org/go-neb/goneb.go b/src/github.com/matrix-org/go-neb/goneb.go index 6afcb56..8fc16f8 100644 --- a/src/github.com/matrix-org/go-neb/goneb.go +++ b/src/github.com/matrix-org/go-neb/goneb.go @@ -27,6 +27,7 @@ import ( _ "github.com/matrix-org/go-neb/services/guggy" _ "github.com/matrix-org/go-neb/services/imgur" _ "github.com/matrix-org/go-neb/services/jira" + _ "github.com/matrix-org/go-neb/services/mtgcard" _ "github.com/matrix-org/go-neb/services/rssbot" _ "github.com/matrix-org/go-neb/services/slackapi" _ "github.com/matrix-org/go-neb/services/travisci" diff --git a/src/github.com/matrix-org/go-neb/services/mtgcard/mtgcard.go b/src/github.com/matrix-org/go-neb/services/mtgcard/mtgcard.go new file mode 100644 index 0000000..d5cf346 --- /dev/null +++ b/src/github.com/matrix-org/go-neb/services/mtgcard/mtgcard.go @@ -0,0 +1,168 @@ +// Package mtgcard implements a Service which adds !commands and mentions for scryfall api. +package mtgcard + +import ( + "bytes" + "encoding/json" + "fmt" + "html" + "net/http" + "net/url" + "regexp" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/matrix-org/go-neb/types" + "github.com/matrix-org/gomatrix" +) + +// ServiceType of the Mtgcard service. +const ServiceType = "mtgcard" + +// Matches [[ then word separated with spaces then ]]. E.g "[[card Name]] +var cardRegex = regexp.MustCompile(`\[\[\w+(\s*\w+)*\]\]`) + +type scryfallSearch struct { + Name string `json:"name"` + ScryfallURI string `json:"scryfall_uri"` + Euro string `json:"eur"` + PurchaseURIs struct { + Cardmarket string `json:cardmarket` + } `json:purchase_uris` + ImageURIs struct { + Normal string `json:"normal"` + Small string `json:"small"` + } `json:"image_uris"` +} + +// Service contains the Config fields for the Mtgcard Service. +type Service struct { + types.DefaultService +} + +// Commands supported: +// !mtgcard some search query without quotes +// Responds with card information into the same room as the command. +func (s *Service) Commands(client *gomatrix.Client) []types.Command { + return []types.Command{ + types.Command{ + Path: []string{"mtgcard"}, + Command: func(roomID, userID string, args []string) (interface{}, error) { + return s.cmdScryfall(client, roomID, userID, args) + }, + }, + } +} + +// Expansions expands card mentions represented as: +// [[ possible name of card ]] +// and responds with card information into the same room as the expansion +func (s *Service) Expansions(cli *gomatrix.Client) []types.Expansion { + return []types.Expansion{ + types.Expansion{ + Regexp: cardRegex, + Expand: func(roomID, userID string, cardMentions []string) interface{} { + return s.expandCard(roomID, userID, cardMentions) + }, + }, + } +} + +// cmdScryfall processes a command by calling the query and returning a message with card info +func (s *Service) cmdScryfall(client *gomatrix.Client, roomID, userID string, args []string) (interface{}, error) { + // only 1 arg which is the text to search for. + query := strings.Join(args, " ") + + scryfallResult, err := s.searchScryfall(query) + if err != nil { + return nil, err + } + if scryfallResult != nil { + return createMessage(scryfallResult), err + } + return nil, err + +} + +// expandCard processes an expansion by calling the query and returning a message with card info +func (s *Service) expandCard(roomID, userID string, cardMentions []string) interface{} { + // cardMentions => ["[[magic card]]"] + + logger := log.WithField("cardMentions", cardMentions) + logger.WithFields(log.Fields{ + "room_id": roomID, + "user_id": userID, + }).Print("Expanding card mention") + + cardMentions[0] = strings.TrimPrefix(cardMentions[0], "[[") + cardMentions[0] = strings.TrimSuffix(cardMentions[0], "]]") + scryfallResult, err := s.searchScryfall(cardMentions[0]) + if err != nil { + return err + } + if scryfallResult != nil { + return createMessage(scryfallResult) + } + return nil +} + +// createMessage returns a nicely formatted matrix.HTMLMessage from a query result +func createMessage(result *scryfallSearch) gomatrix.HTMLMessage { + + var htmlBuffer bytes.Buffer + var plainBuffer bytes.Buffer + message := fmt.Sprintf( + "", + result.ImageURIs.Normal, + html.EscapeString(result.Name), + result.ScryfallURI, + result.PurchaseURIs.Cardmarket, + result.Euro, + ) + htmlBuffer.WriteString(message) + plainBuffer.WriteString(fmt.Sprintf("$s, $s, $s", result.Name, result.ScryfallURI, result.Euro)) + + return gomatrix.HTMLMessage{ + Body: plainBuffer.String(), + MsgType: "m.notice", + Format: "org.matrix.custom.html", + FormattedBody: htmlBuffer.String(), + } +} + +// searchScryfall queries Scryfall API for card info +func (s *Service) searchScryfall(query string) (*scryfallSearch, error) { + log.Info("Searching scryfall for ", query) + u, err := url.Parse("https://api.scryfall.com/cards/named") + if err != nil { + return nil, err + } + q := u.Query() + q.Set("fuzzy", query) + u.RawQuery = q.Encode() + res, err := http.Get(u.String()) + if res != nil { + defer res.Body.Close() + } + if err != nil { + log.Error("Problem searching scryfall: ", err) + return nil, err + } + if res.StatusCode == http.StatusNotFound { + return nil, nil + } + var search scryfallSearch + if err := json.NewDecoder(res.Body).Decode(&search); err != nil { + return nil, err + } + log.Info("Search scryfall returned ", search) + return &search, nil +} + +func init() { + types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service { + return &Service{ + DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType), + } + }) +}