From f4497aef7e088fa0e21e96a2684ed95046c84e87 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 16 Jul 2025 11:40:25 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20mkflex=20command?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Taskfile.yml | 16 +++++ cmd/mkflex/main.go | 27 +++++++ convids/models.go | 13 +++- internal/cli/mkflex/app.go | 53 ++++++++++++++ mkflex/logic.go | 142 +++++++++++++++++++++++++++++++++++++ mkflex/models.go | 38 ++++++++++ types/yaml.go | 45 ++++++++++++ 7 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 cmd/mkflex/main.go create mode 100644 internal/cli/mkflex/app.go create mode 100644 mkflex/logic.go create mode 100644 mkflex/models.go create mode 100644 types/yaml.go diff --git a/Taskfile.yml b/Taskfile.yml index 17e1e0c..50c0b63 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -35,6 +35,7 @@ tasks: internal: true cmds: - go build -o build/ ./cmd/{{.CMD}} + build-convids: desc: Builds the convids command sources: @@ -45,6 +46,7 @@ tasks: - task: cmd-build vars: CMD: convids + build-cool-down: desc: Builds the cool-down command source: @@ -58,6 +60,7 @@ tasks: - task: cmd-build vars: CMD: cool-down + build-all: desc: Builds all available commands sources: @@ -70,6 +73,19 @@ tasks: vars: CMD: "*" + install-mkflex: + desc: Installs the mkflex command + source: + - cmd/mkflex/**/*.go + - convids/**/*.go + - internal/cli/mkflex/**/*.go + - mkflex/**/*.go + - types/**/*.go + generates: + - '{{.BIN}}/mkflex' + cmds: + - go install ./cmd/mkflex + install-cool-down: desc: Installs the cool-down command source: diff --git a/cmd/mkflex/main.go b/cmd/mkflex/main.go new file mode 100644 index 0000000..4d7bdd7 --- /dev/null +++ b/cmd/mkflex/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "fmt" + "os" + + c "codeberg.org/danjones000/utils/cli/context" + e "codeberg.org/danjones000/utils/cli/err" + mkcli "codeberg.org/danjones000/utils/internal/cli/mkflex" +) + +const dataPath = "/home/drj/WeboNextCloud/Computer/dotfiles/bigbad/shows.yml" +const flexPath = "/home/drj/.flexget/config.yml" +const flexTemp = "/home/drj/.flexget/config.temp.yml" + +func main() { + ctx, done := c.SelfCancelingContextFromBackground() + defer done() + + app, err := mkcli.NewApp(ctx, os.Args[0], os.Args[1:], dataPath, flexTemp, flexPath) + e.HandleErr(err) + + err = app.Run(ctx) + e.HandleErr(err) + + fmt.Println("Generated flexget config") +} diff --git a/convids/models.go b/convids/models.go index ce23ca8..5473ffd 100644 --- a/convids/models.go +++ b/convids/models.go @@ -1,6 +1,10 @@ package convids -import "regexp" +import ( + "regexp" + + "codeberg.org/danjones000/utils/types" +) type Data struct { Config *Config @@ -28,6 +32,13 @@ type Show struct { Url bool Backup string Sources []string + Flexget struct { + Name string + Begin types.IntOrString + AlternateName []string `yaml:"alternate_name"` + Exact bool + Skip bool + } re *regexp.Regexp } diff --git a/internal/cli/mkflex/app.go b/internal/cli/mkflex/app.go new file mode 100644 index 0000000..8c4ff6c --- /dev/null +++ b/internal/cli/mkflex/app.go @@ -0,0 +1,53 @@ +package mkflex + +import ( + "context" + "os" + + conutils "codeberg.org/danjones000/utils/convids" + mkutils "codeberg.org/danjones000/utils/mkflex" + "gopkg.in/yaml.v3" +) + +func NewApp(ctx context.Context, name string, args []string, dataPath, templatePath, outPath string) (*App, error) { + var err error + a := App{ + Name: name, + Path: outPath, + } + + a.Data, err = conutils.NewData(dataPath) + if err != nil { + return nil, err + } + + a.Config, err = mkutils.NewConfig(templatePath) + if err != nil { + return nil, err + } + + return &a, nil +} + +type App struct { + Name string + Data *conutils.Data + Path string + Config *mkutils.Config +} + +func (a *App) Run(ctx context.Context) error { + out, err := os.Create(a.Path) + if err != nil { + return err + } + enc := yaml.NewEncoder(out) + enc.SetIndent(2) + + if err := mkutils.AddShows(a.Config, a.Data); err != nil { + return err + } + delete(a.Config.Templates, "x-aliases") + + return enc.Encode(a.Config) +} diff --git a/mkflex/logic.go b/mkflex/logic.go new file mode 100644 index 0000000..963a4d4 --- /dev/null +++ b/mkflex/logic.go @@ -0,0 +1,142 @@ +package mkflex + +import ( + "cmp" + "errors" + "fmt" + "os" + + conutils "codeberg.org/danjones000/utils/convids" + "gopkg.in/yaml.v3" +) + +var ErrMissingAliases = errors.New("missing aliases") +var ErrMissingShowGrp = errors.New("missing show group") + +func NewConfig(path string) (*Config, error) { + //nolint:gosec // I don't care if this is user input + f, err := os.Open(path) + if err != nil { + return nil, err + } + ydec := yaml.NewDecoder(f) + var conf Config + err = ydec.Decode(&conf) + if err != nil { + return nil, err + } + + return &conf, nil +} + +func AddShows(conf *Config, data *conutils.Data) error { + aliases, ok := conf.Templates["x-aliases"] + reg, anime, err := getAliases(aliases, ok) + + if err != nil { + return nil + } + + nonEng := (*data.Shows)["non-eng"] + aniShow := (*data.Shows)["anime"] + + dor, err := getDorama(nonEng, reg) + if err != nil { + return nil + } + conf.Templates["dorama"] = Template{Series: dor} + + aniSer, err := getAnime(aniShow, anime) + if err != nil { + return nil + } + conf.Templates["anime"] = Template{Series: aniSer} + all, err := getAll(data, reg) + if err != nil { + return nil + } + conf.Templates["shows"] = Template{Series: all} + + return nil +} + +func getAliases(aliases Template, ok bool) (reg, anime *Series, err error) { + if !ok { + err = ErrMissingAliases + return + } + + for _, alias := range aliases.Series { + if rF, ok := alias["x-regular"]; ok { + reg = &rF + } + if rA, ok := alias["x-anime"]; ok { + anime = &rA + } + } + + if reg == nil { + err = errors.Join(err, fmt.Errorf("%w: %s", ErrMissingAliases, "x-regular")) + } + + if anime == nil { + err = errors.Join(err, fmt.Errorf("%w: %s", ErrMissingAliases, "x-anime")) + } + + return +} + +func getShow(show conutils.Show, sh Series) SeriesGroups { + if !show.Flexget.Begin.IsZero() { + sh.Begin = show.Flexget.Begin + } + sh.AlternameName = show.Flexget.AlternateName + sh.Exact = show.Flexget.Exact + name := cmp.Or(show.Flexget.Name, show.Name, show.Pattern) + return SeriesGroups{name: sh} +} + +func getDorama(nonEng *conutils.Shows, tmp *Series) ([]SeriesGroups, error) { + if len(*nonEng) == 0 { + return nil, fmt.Errorf("%w: %s", ErrMissingShowGrp, "non-eng") + } + grp := make([]SeriesGroups, 0, len(*nonEng)) + + for _, show := range *nonEng { + if !show.Flexget.Skip { + grp = append(grp, getShow(*show, *tmp)) + } + } + + return grp, nil +} + +func getAnime(anime *conutils.Shows, tmp *Series) ([]SeriesGroups, error) { + grp := make([]SeriesGroups, 0, len(*anime)) + + for _, show := range *anime { + if !show.Flexget.Skip { + grp = append(grp, getShow(*show, *tmp)) + } + } + + return grp, nil +} + +func getAll(data *conutils.Data, tmp *Series) ([]SeriesGroups, error) { + grp := make([]SeriesGroups, 0, 100) + + for _, group := range data.Config.Groups { + shGrp, ok := (*data.Shows)[group] + if group == "old" || shGrp == nil || !ok { + continue + } + for _, show := range *shGrp { + if !show.Flexget.Skip && !show.Anime { + grp = append(grp, getShow(*show, *tmp)) + } + } + } + + return grp, nil +} diff --git a/mkflex/models.go b/mkflex/models.go new file mode 100644 index 0000000..40d0cf2 --- /dev/null +++ b/mkflex/models.go @@ -0,0 +1,38 @@ +package mkflex + +import "codeberg.org/danjones000/utils/types" + +type Config struct { + Templates map[string]Template `yaml:"templates"` + Tasks map[string]any `yaml:"tasks"` +} + +type Template struct { + Series []SeriesGroups `yaml:"series,omitempty"` + Aria2 map[string]any `yaml:"aria2,omitempty"` + ConMag map[string]any `yaml:"convert_magnet,omitempty"` + Download string `yaml:"download,omitempty"` + Notify map[string]any `yaml:"notify,omitempty"` +} + +type SeriesGroups map[string]Series + +type Identifier string + +const ( + Sequence Identifier = "sequence" + Episode Identifier = "ep" +) + +type Series struct { + Quality string `yaml:"quality,omitempty"` + IdentifiedBy Identifier `yaml:"identified_by,omitempty"` + Begin types.IntOrString `yaml:"begin,omitempty"` + Exact bool `yaml:"exact,omitempty"` + AlternameName []string `yaml:"alternate_name,omitempty"` +} + +type Aria struct { + Path string `yaml:"path"` + Server string `yaml:"server"` +} diff --git a/types/yaml.go b/types/yaml.go new file mode 100644 index 0000000..4fa1d1b --- /dev/null +++ b/types/yaml.go @@ -0,0 +1,45 @@ +package types + +import "gopkg.in/yaml.v3" + +type YamlTypeError struct { + Node *yaml.Node +} + +func (ye *YamlTypeError) Error() string { + return ye.Node.Tag + " is not a valid type for this field" +} + +type IntOrString struct { + intVal int + strVal string + isInt bool +} + +func (is IntOrString) MarshalYAML() (any, error) { + if is.isInt { + return is.intVal, nil + } + return is.strVal, nil +} + +func (is *IntOrString) UnmarshalYAML(value *yaml.Node) error { + if value.Tag == `!!int` { + is.isInt = true + return value.Decode(&is.intVal) + } + + if value.Tag == `!!str` { + is.isInt = false + return value.Decode(&is.strVal) + } + + return &YamlTypeError{Node: value} +} + +func (is IntOrString) IsZero() bool { + if is.isInt { + return is.intVal == 0 + } + return is.strVal == "" +}