2023-05-14 15:49:04 -07:00
|
|
|
package main
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"archive/zip"
|
|
|
|
|
"flag"
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
2023-05-14 17:11:49 -07:00
|
|
|
"unicode"
|
2023-05-14 15:49:04 -07:00
|
|
|
|
2023-05-14 16:35:29 -07:00
|
|
|
"github.com/fatih/camelcase"
|
2023-05-14 15:49:04 -07:00
|
|
|
"github.com/firestuff/dnd/internal"
|
2023-05-21 10:21:59 -07:00
|
|
|
"github.com/gobwas/glob"
|
2023-05-14 15:49:04 -07:00
|
|
|
"github.com/samber/lo"
|
|
|
|
|
"golang.org/x/exp/slog"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var removeWords = map[string]bool{
|
2023-05-14 16:35:29 -07:00
|
|
|
"+": true,
|
2023-05-14 17:11:49 -07:00
|
|
|
"-": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"": true,
|
2023-05-14 17:11:49 -07:00
|
|
|
"-diamond": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"1": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"1)": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"2": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"2)": true,
|
|
|
|
|
"$5": true,
|
|
|
|
|
"rewards": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"diamond": true,
|
|
|
|
|
"gridless": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"(gridless)": true,
|
|
|
|
|
"(gridless": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"high": true,
|
2023-05-14 17:11:49 -07:00
|
|
|
"high-res": true,
|
|
|
|
|
"part": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"pt": true,
|
|
|
|
|
"pt.": true,
|
|
|
|
|
"pt.1": true,
|
|
|
|
|
"pt.1)": true,
|
|
|
|
|
"pt.2": true,
|
|
|
|
|
"pt.2)": true,
|
|
|
|
|
"pt.3": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"psd": true,
|
|
|
|
|
"res": true,
|
2023-05-14 16:35:29 -07:00
|
|
|
"roll20": true,
|
2023-05-14 16:21:40 -07:00
|
|
|
"roll20+tokens": true,
|
2023-05-14 18:01:39 -07:00
|
|
|
"support": true,
|
|
|
|
|
"tier": true,
|
2023-05-14 16:35:29 -07:00
|
|
|
"tokens": true,
|
2023-05-21 10:21:59 -07:00
|
|
|
"(tokens)": true,
|
|
|
|
|
"rewards(1)": true,
|
|
|
|
|
"transparent": true,
|
|
|
|
|
"pngs": true,
|
2023-05-14 15:49:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var actions = map[string]string{
|
2023-05-21 10:21:59 -07:00
|
|
|
"Gridless/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"*/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"*/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**ds_store": "{SKIP}",
|
|
|
|
|
"**/*.db": "{SKIP}",
|
|
|
|
|
"**/*.pdf": "{SKIP}",
|
|
|
|
|
"**/*.mp3": "{SKIP}",
|
|
|
|
|
"**/*.zip": "{SKIP}",
|
|
|
|
|
"__macosx/**": "{SKIP}",
|
|
|
|
|
"**/high resolution/*.jpg": "Maps/{MAPNAME}", // Trust that it's gridless
|
|
|
|
|
"**/high resolution/*.png": "Maps/{MAPNAME}", // Trust that it's gridless
|
|
|
|
|
"**/gridless/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/gridless/*.jpeg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high-res/gridless/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high-res/gridless/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/attic/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/attic/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/basement/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/basement/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/floor 1/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/floor 1/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/floor 2/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high res/gridless/floor 2/*.png": "Maps/{MAPNAME}",
|
|
|
|
|
"**/high resolution/gridless/*.jpg": "Maps/{MAPNAME}",
|
|
|
|
|
"**/grid/*.jpg": "{SKIP}",
|
|
|
|
|
"**/gridded/*.jpg": "{SKIP}",
|
|
|
|
|
"**/high-res/grid/*.jpg": "{SKIP}",
|
|
|
|
|
"**/high res/grid/*.jpg": "{SKIP}",
|
|
|
|
|
"**/high resolution/grid/*.jpg": "{SKIP}",
|
|
|
|
|
"**/creature tokens/*.png": "Creatures",
|
|
|
|
|
"**/creature tokens/variants/*.png": "Creatures",
|
|
|
|
|
"**/map tokens/*.png": "Maps/{MAPNAME}/Objects",
|
|
|
|
|
"**/tokens/*.png": "Maps/{MAPNAME}/Objects",
|
|
|
|
|
"**/tokens/*.jpg": "Maps/{MAPNAME}/Objects",
|
|
|
|
|
"**/roll20/**": "{SKIP}",
|
2023-05-14 15:49:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var root = flag.String("root", "", "root directory to write to")
|
|
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
|
l := slog.New(slog.NewTextHandler(os.Stderr, nil))
|
|
|
|
|
|
|
|
|
|
flag.Parse()
|
|
|
|
|
|
|
|
|
|
if *root == "" {
|
|
|
|
|
l.Error("please specify --root")
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, path := range flag.Args() {
|
|
|
|
|
zipL := l.With("zipPath", path)
|
|
|
|
|
|
|
|
|
|
s := lo.Must(os.Stat(path))
|
|
|
|
|
if s.Size() == 0 {
|
|
|
|
|
zipL.Warn("SKIP")
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ok := importZIP(zipL, path)
|
|
|
|
|
if !ok {
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lo.Must0(os.Remove(path))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func importZIP(l *slog.Logger, path string) bool {
|
|
|
|
|
name := mapName(path)
|
|
|
|
|
l = l.With("mapName", name)
|
|
|
|
|
|
|
|
|
|
z := lo.Must(zip.OpenReader(path))
|
|
|
|
|
defer z.Close()
|
|
|
|
|
|
|
|
|
|
for _, file := range z.File {
|
|
|
|
|
if strings.HasSuffix(file.Name, "/") {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 16:21:40 -07:00
|
|
|
sig := strings.ToLower(internal.PathSig(file.Name))
|
2023-05-21 10:21:59 -07:00
|
|
|
dst := ""
|
|
|
|
|
|
|
|
|
|
for pattern, action := range actions {
|
|
|
|
|
g := glob.MustCompile(pattern)
|
|
|
|
|
if g.Match(sig) {
|
|
|
|
|
dst = action
|
|
|
|
|
break
|
|
|
|
|
}
|
2023-05-14 15:49:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dst == "" {
|
|
|
|
|
l.Error("unknown file signature",
|
|
|
|
|
"filePath", file.Name,
|
|
|
|
|
"signature", sig,
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dst == "{SKIP}" {
|
|
|
|
|
l.Info("SKIP",
|
|
|
|
|
"src", file.Name,
|
|
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dst = strings.ReplaceAll(dst, "{MAPNAME}", name)
|
2023-05-14 17:11:49 -07:00
|
|
|
dst = printable(filepath.Join(*root, dst, filepath.Base(file.Name)))
|
2023-05-14 15:49:04 -07:00
|
|
|
|
|
|
|
|
l.Info("COPY",
|
|
|
|
|
"src", file.Name,
|
|
|
|
|
"dst", dst,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
lo.Must0(os.MkdirAll(filepath.Dir(dst), 0755))
|
|
|
|
|
dstFile := lo.Must(os.OpenFile(dst, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644))
|
|
|
|
|
srcFile := lo.Must(file.Open())
|
|
|
|
|
|
|
|
|
|
lo.Must(io.Copy(dstFile, srcFile))
|
|
|
|
|
|
|
|
|
|
dstFile.Close()
|
|
|
|
|
srcFile.Close()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mapName(path string) string {
|
|
|
|
|
withoutZIP := strings.TrimSuffix(filepath.Base(path), ".zip")
|
|
|
|
|
parts := strings.Split(withoutZIP, " ")
|
|
|
|
|
|
2023-05-14 16:35:29 -07:00
|
|
|
if len(parts) == 1 {
|
|
|
|
|
parts = camelcase.Split(parts[0])
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 15:49:04 -07:00
|
|
|
i := len(parts) - 1
|
2023-05-21 10:21:59 -07:00
|
|
|
for i >= 0 && (removeWords[strings.ToLower(parts[i])] || strings.HasPrefix(parts[i], "[")) {
|
2023-05-14 15:49:04 -07:00
|
|
|
i--
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 18:01:39 -07:00
|
|
|
parts = parts[:i+1]
|
|
|
|
|
|
|
|
|
|
for len(parts) > 0 && removeWords[strings.ToLower(parts[0])] {
|
|
|
|
|
parts = parts[1:]
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 17:11:49 -07:00
|
|
|
if len(parts) == 0 {
|
|
|
|
|
panic(path)
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 18:01:39 -07:00
|
|
|
return strings.Join(parts, " ")
|
2023-05-14 15:49:04 -07:00
|
|
|
}
|
2023-05-14 17:11:49 -07:00
|
|
|
|
|
|
|
|
func printable(in string) string {
|
|
|
|
|
return strings.Map(func(r rune) rune {
|
|
|
|
|
if unicode.IsPrint(r) {
|
|
|
|
|
return r
|
|
|
|
|
}
|
|
|
|
|
return -1
|
|
|
|
|
}, in)
|
|
|
|
|
}
|