diff --git a/.gitignore b/.gitignore index 51d9d2e..2e1af13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ vendor -go-mtg-vk *.swp hosts -.idea \ No newline at end of file +.idea +.venv \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ae5d19d..3e80994 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ FROM golang:1.15.4-alpine3.12 COPY . /go/src/go-mtg-vk -WORKDIR /go/src/go-mtg-vk +WORKDIR /go/src/go-mtg-vk/cmd/go-mtg-vk RUN go install WORKDIR /go/bin RUN mkdir logs diff --git a/main.go b/cmd/go-mtg-vk/main.go similarity index 82% rename from main.go rename to cmd/go-mtg-vk/main.go index a492687..4af3303 100644 --- a/main.go +++ b/cmd/go-mtg-vk/main.go @@ -6,8 +6,8 @@ import ( "os" "time" - "gitlab.com/flygrounder/go-mtg-vk/vk" "github.com/gin-gonic/gin" + "gitlab.com/flygrounder/go-mtg-vk/internal/vk" ) func main() { @@ -17,5 +17,5 @@ func main() { log.SetOutput(logFile) r := gin.Default() r.POST("callback/message", vk.HandleMessage) - _ = r.Run(":80") + _ = r.Run(":8000") } diff --git a/docker-compose-dev.yaml b/docker-compose-dev.yaml new file mode 100644 index 0000000..d194851 --- /dev/null +++ b/docker-compose-dev.yaml @@ -0,0 +1,16 @@ +version: "3.3" +services: + web: + build: . + environment: + - VK_TOKEN + - VK_SECRET_KEY + - VK_GROUP_ID + - VK_CONFIRMATION_STRING + + ports: + - "127.0.0.1:8888:8000" + restart: "always" + redis: + image: "redis:6.0.9" + restart: "always" diff --git a/docker-compose.yaml b/docker-compose.yaml index a482029..b0a433d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -9,7 +9,7 @@ services: - VK_CONFIRMATION_STRING ports: - - "127.0.0.1:8888:80" + - "127.0.0.1:8888:8000" restart: "always" redis: image: "redis:6.0.9" diff --git a/go.mod b/go.mod index 7c1fe4b..c536ead 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/onsi/gomega v1.7.1 // indirect github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.5.1 + github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da // indirect golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 // indirect golang.org/x/sys v0.0.0-20191029155521-f43be2a4598c // indirect diff --git a/go.sum b/go.sum index 1987d2b..176d3e1 100644 --- a/go.sum +++ b/go.sum @@ -52,6 +52,9 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/texttheater/golang-levenshtein v1.0.1 h1:+cRNoVrfiwufQPhoMzB6N0Yf/Mqajr6t1lOv8GyGE2U= +github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c h1:HelZ2kAFadG0La9d+4htN4HzQ68Bm2iM9qKMSMES6xg= +github.com/texttheater/golang-levenshtein/levenshtein v0.0.0-20200805054039-cae8b0eaed6c/go.mod h1:JlzghshsemAMDGZLytTFY8C1JQxQPhnatWqNwUXjggo= github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= diff --git a/caching/cache_test.go b/internal/caching/cache_test.go similarity index 98% rename from caching/cache_test.go rename to internal/caching/cache_test.go index f937395..9540720 100644 --- a/caching/cache_test.go +++ b/internal/caching/cache_test.go @@ -43,7 +43,7 @@ func getTestClient() (*CacheClient, *miniredis.Miniredis) { Addr: s.Addr(), }) return &CacheClient{ - Storage: c, + Storage: c, Expiration: 0, }, s } diff --git a/caching/client.go b/internal/caching/client.go similarity index 100% rename from caching/client.go rename to internal/caching/client.go diff --git a/caching/secrets.go b/internal/caching/secrets.go similarity index 100% rename from caching/secrets.go rename to internal/caching/secrets.go diff --git a/cardsinfo/format.go b/internal/cardsinfo/format.go similarity index 84% rename from cardsinfo/format.go rename to internal/cardsinfo/format.go index a575b65..9b5f155 100644 --- a/cardsinfo/format.go +++ b/internal/cardsinfo/format.go @@ -8,7 +8,7 @@ func FormatCardPrices(name string, prices []CardPrice) string { message := fmt.Sprintf("Оригинальное название: %v\n", name) message += fmt.Sprintf("Результатов: %v\n", len(prices)) for i, v := range prices { - message += fmt.Sprintf("%v. %v", i + 1, v.Format()) + message += fmt.Sprintf("%v. %v", i+1, v.Format()) } return message } diff --git a/cardsinfo/names.go b/internal/cardsinfo/names.go similarity index 79% rename from cardsinfo/names.go rename to internal/cardsinfo/names.go index 19faec5..e9e4ed1 100644 --- a/cardsinfo/names.go +++ b/internal/cardsinfo/names.go @@ -2,6 +2,8 @@ package cardsinfo import ( "encoding/json" + "gitlab.com/flygrounder/go-mtg-vk/internal/dicttranslate" + "io" "io/ioutil" "net/http" "net/url" @@ -18,9 +20,13 @@ func GetNameByCardId(set string, number string) string { return GetCardByUrl(path) } -func GetOriginalName(name string) string { +func GetOriginalName(name string, dict io.Reader) string { path := ScryfallUrl + "/cards/named?fuzzy=" + ApplyFilters(name) - return GetCardByUrl(path) + result := GetCardByUrl(path) + if result == "" && dict != nil { + result, _ = dicttranslate.FindFromReader(name, dict, 5) + } + return result } func ApplyFilters(name string) string { diff --git a/cardsinfo/names_test.go b/internal/cardsinfo/names_test.go similarity index 63% rename from cardsinfo/names_test.go rename to internal/cardsinfo/names_test.go index b53b455..31cb59e 100644 --- a/cardsinfo/names_test.go +++ b/internal/cardsinfo/names_test.go @@ -2,36 +2,37 @@ package cardsinfo import ( "github.com/stretchr/testify/assert" + "strings" "testing" ) func TestGetCardByStringFull(t *testing.T) { - name := GetOriginalName("Шок") + name := GetOriginalName("Шок", nil) assert.Equal(t, "Shock", name) } func TestGetCardByStringSplit(t *testing.T) { - name := GetOriginalName("commit") + name := GetOriginalName("commit", nil) assert.Equal(t, "Commit // Memory", name) } func TestGetCardByStringDouble(t *testing.T) { - name := GetOriginalName("Legion's landing") + name := GetOriginalName("Legion's landing", nil) assert.Equal(t, "Legion's Landing | Adanto, the First Fort", name) } func TestGetCardByStringPrefix(t *testing.T) { - name := GetOriginalName("Тефери, герой") + name := GetOriginalName("Тефери, герой", nil) assert.Equal(t, "Teferi, Hero of Dominaria", name) } func TestGetCardByStringEnglish(t *testing.T) { - name := GetOriginalName("Teferi, Hero of Dominaria") + name := GetOriginalName("Teferi, Hero of Dominaria", nil) assert.Equal(t, "Teferi, Hero of Dominaria", name) } func TestGetCardByStringWrong(t *testing.T) { - name := GetOriginalName("fwijefiwjfew") + name := GetOriginalName("fwijefiwjfew", nil) assert.Equal(t, "", name) } @@ -44,3 +45,9 @@ func TestGetCardBySetIdWrong(t *testing.T) { name := GetNameByCardId("DOM", "1207") assert.Equal(t, "", name) } + +func TestGetCardByStringDict(t *testing.T) { + dictContent := "{\"n0suchc8rdc8n3x1s1\":\"Success\"}" + name := GetOriginalName("n0suchc8rdc8n3x1s1", strings.NewReader(dictContent)) + assert.Equal(t, "Success", name) +} diff --git a/cardsinfo/price_format_test.go b/internal/cardsinfo/price_format_test.go similarity index 84% rename from cardsinfo/price_format_test.go rename to internal/cardsinfo/price_format_test.go index 341a2fd..2b50477 100644 --- a/cardsinfo/price_format_test.go +++ b/internal/cardsinfo/price_format_test.go @@ -8,10 +8,10 @@ import ( func TestFormat(t *testing.T) { data := []CardPrice{ &TcgCardPrice{ - Name: "Green lotus", - PriceFoil: "22.8", - Link: "scg.com/1", - Edition: "alpha", + Name: "Green lotus", + PriceFoil: "22.8", + Link: "scg.com/1", + Edition: "alpha", }, &TcgCardPrice{ Name: "White lotus", diff --git a/cardsinfo/prices.go b/internal/cardsinfo/prices.go similarity index 93% rename from cardsinfo/prices.go rename to internal/cardsinfo/prices.go index 97dd911..9a96aa2 100644 --- a/cardsinfo/prices.go +++ b/internal/cardsinfo/prices.go @@ -71,12 +71,12 @@ func GetPricesTcg(name string) ([]CardPrice, error) { if card.Prices.USD == "" && card.Prices.USDFoil == "" { continue } - cardPrice := &TcgCardPrice { - Edition: edition, - Price: card.Prices.USD, + cardPrice := &TcgCardPrice{ + Edition: edition, + Price: card.Prices.USD, PriceFoil: card.Prices.USDFoil, - Name: card.Name, - Link: card.PurchaseURIs.TCGPlayer, + Name: card.Name, + Link: card.PurchaseURIs.TCGPlayer, } prices = append(prices, cardPrice) } diff --git a/cardsinfo/prices_test.go b/internal/cardsinfo/prices_test.go similarity index 100% rename from cardsinfo/prices_test.go rename to internal/cardsinfo/prices_test.go diff --git a/cardsinfo/structs.go b/internal/cardsinfo/structs.go similarity index 90% rename from cardsinfo/structs.go rename to internal/cardsinfo/structs.go index 4c49d98..7d5000a 100644 --- a/cardsinfo/structs.go +++ b/internal/cardsinfo/structs.go @@ -10,12 +10,12 @@ type CardPrice interface { } type TcgCardPrice struct { - FullArt bool - Name string - Price string + FullArt bool + Name string + Price string PriceFoil string - Link string - Edition string + Link string + Edition string } func (t *TcgCardPrice) Format() string { @@ -30,9 +30,9 @@ func formatTcgPrice(price string) string { } type ScgCardPrice struct { - Price string + Price string Edition string - Link string + Link string } func (s *ScgCardPrice) Format() string { diff --git a/internal/dicttranslate/find.go b/internal/dicttranslate/find.go new file mode 100644 index 0000000..f9c5ffd --- /dev/null +++ b/internal/dicttranslate/find.go @@ -0,0 +1,23 @@ +package dicttranslate + +import ( + "encoding/json" + "io" + "io/ioutil" +) + +func find(query string, dict map[string]string, maxDist int) (string, bool) { + var keys []string + for i := range dict { + keys = append(keys, i) + } + key, f := match(query, keys, maxDist) + return dict[key], f +} + +func FindFromReader(query string, reader io.Reader, maxDist int) (string, bool) { + content, _ := ioutil.ReadAll(reader) + dict := map[string]string{} + _ = json.Unmarshal(content, &dict) + return find(query, dict, maxDist) +} diff --git a/internal/dicttranslate/find_test.go b/internal/dicttranslate/find_test.go new file mode 100644 index 0000000..04577b9 --- /dev/null +++ b/internal/dicttranslate/find_test.go @@ -0,0 +1,33 @@ +package dicttranslate + +import ( + "github.com/stretchr/testify/assert" + "strings" + "testing" +) + +func TestFindEmpty(t *testing.T) { + dict := map[string]string{} + _, f := find("", dict, 0) + assert.False(t, f) +} + +func TestFindEntry(t *testing.T) { + dict := map[string]string{ + "entry": "value", + } + val, f := find("entry", dict, 0) + assert.True(t, f) + assert.Equal(t, "value", val) +} + +func TestFindFromReaderFail(t *testing.T) { + _, f := FindFromReader("entry", strings.NewReader("{}"), 0) + assert.False(t, f) +} + +func TestFindFromReaderSuccess(t *testing.T) { + value, f := FindFromReader("entry", strings.NewReader("{\"entry\":\"value\"}"), 0) + assert.True(t, f) + assert.Equal(t, "value", value) +} diff --git a/internal/dicttranslate/match.go b/internal/dicttranslate/match.go new file mode 100644 index 0000000..cabd58b --- /dev/null +++ b/internal/dicttranslate/match.go @@ -0,0 +1,21 @@ +package dicttranslate + +import "github.com/texttheater/golang-levenshtein/levenshtein" + +func match(query string, opts []string, maxDist int) (string, bool) { + bestInd := -1 + bestDist := 0 + for i, s := range opts { + cfg := levenshtein.DefaultOptions + cfg.SubCost = 1 + dist := levenshtein.DistanceForStrings([]rune(s), []rune(query), cfg) + if dist <= maxDist && (bestInd == -1 || dist < bestDist) { + bestInd = i + bestDist = dist + } + } + if bestInd == -1 { + return "", false + } + return opts[bestInd], true +} diff --git a/internal/dicttranslate/match_test.go b/internal/dicttranslate/match_test.go new file mode 100644 index 0000000..5111aad --- /dev/null +++ b/internal/dicttranslate/match_test.go @@ -0,0 +1,52 @@ +package dicttranslate + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMatch(t *testing.T) { + type testCase struct { + name string + query string + opts []string + shouldFind bool + match string + } + tests := []testCase{ + { + name: "No options", + query: "opt", + opts: []string{}, + shouldFind: false, + }, + { + name: "Match one", + query: "option", + opts: []string{"opt1on"}, + shouldFind: true, + match: "opt1on", + }, + { + name: "Match exact", + query: "opt1on", + opts: []string{"option", "opt1on"}, + shouldFind: true, + match: "opt1on", + }, + { + name: "Do not match bad options", + query: "random", + opts: []string{"option", "opt1on"}, + shouldFind: false, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + val, f := match(test.query, test.opts, 1) + assert.Equal(t, test.shouldFind, f) + assert.Equal(t, test.match, val) + }) + } +} diff --git a/vk/handlers.go b/internal/vk/handlers.go similarity index 89% rename from vk/handlers.go rename to internal/vk/handlers.go index f41308c..520cb38 100644 --- a/vk/handlers.go +++ b/internal/vk/handlers.go @@ -4,13 +4,16 @@ import ( "errors" "log" "net/http" + "os" "strings" - "gitlab.com/flygrounder/go-mtg-vk/caching" - "gitlab.com/flygrounder/go-mtg-vk/cardsinfo" "github.com/gin-gonic/gin" + "gitlab.com/flygrounder/go-mtg-vk/internal/caching" + "gitlab.com/flygrounder/go-mtg-vk/internal/cardsinfo" ) +var dictPath = "./assets/additional_cards.json" + func HandleMessage(c *gin.Context) { var req MessageRequest _ = c.BindJSON(&req) @@ -78,7 +81,8 @@ func getCardNameByCommand(command string) (string, error) { number := split[2] name = cardsinfo.GetNameByCardId(set, number) default: - name = cardsinfo.GetOriginalName(command) + dict, _ := os.Open(dictPath) + name = cardsinfo.GetOriginalName(command, dict) } return name, nil } diff --git a/vk/message.go b/internal/vk/message.go similarity index 100% rename from vk/message.go rename to internal/vk/message.go diff --git a/vk/secrets.go b/internal/vk/secrets.go similarity index 100% rename from vk/secrets.go rename to internal/vk/secrets.go diff --git a/vk/structs.go b/internal/vk/structs.go similarity index 100% rename from vk/structs.go rename to internal/vk/structs.go diff --git a/scripts/spoiler_fetcher.py b/scripts/spoiler_fetcher.py new file mode 100644 index 0000000..c4f5e9b --- /dev/null +++ b/scripts/spoiler_fetcher.py @@ -0,0 +1,31 @@ +from json import dumps + +import requests +from lxml import etree + +URL_TEMPLATE = "https://magic.wizards.com/{}/articles/archive/card-image-gallery/{}" +OUTPUT_FILE_TEMPLATE = "{}.json" + + +def get_card_names(language, set_name): + spoiler_url = URL_TEMPLATE.format(language, set_name) + response = requests.get(spoiler_url) + dom = etree.HTML(response.content.decode()) + card_names = dom.xpath('//div[@class="resizing-cig"]//p/text()') + return [str(name).strip() for name in card_names] + + +def match_names(keys, values): + return dict(zip(keys, values)) + + +set_name = input("Введите сет: ") + +russian_names = get_card_names("ru", set_name) +english_names = get_card_names("en", set_name) + +match = match_names(russian_names, english_names) +print(match) + +with open(OUTPUT_FILE_TEMPLATE.format(set_name), 'w') as output: + output.write(dumps(match))