Skip to content
This repository has been archived by the owner on Jun 28, 2023. It is now read-only.

Add mtgcard service #267

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/github.com/matrix-org/go-neb/goneb.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
168 changes: 168 additions & 0 deletions src/github.com/matrix-org/go-neb/services/mtgcard/mtgcard.go
Original file line number Diff line number Diff line change
@@ -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(`\[\[[\p{L}|\p{Po}+|\s*]+\]\]`)

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(
"<ul><li><a href=%s>%s</a>\t<a href=%s>(SF)</a>\t<a href=%s>(%s&euro;)</a></li></ul>",
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),
}
})
}