/* Copyright © 2023-2024 Thomas von Dein This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ package main import ( "errors" "fmt" "io" "os" "path/filepath" "runtime" "strings" "github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/posflag" "github.com/knadh/koanf/v2" flag "github.com/spf13/pflag" ) const ( VERSION string = "0.1.2" Baseuri string = "https://www.kleinanzeigen.de" Listuri string = "/s-bestandsliste.html" Defaultdir string = "." DefaultTemplate string = "Title: {{.Title}}\nPrice: {{.Price}}\nId: {{.Id}}\n" + "Category: {{.Category}}\nCondition: {{.Condition}}\nCreated: {{.Created}}\n\n{{.Text}}\n" DefaultTemplateWin string = "Title: {{.Title}}\r\nPrice: {{.Price}}\r\nId: {{.Id}}\r\n" + "Category: {{.Category}}\r\nCondition: {{.Condition}}\r\nCreated: {{.Created}}\r\n\r\n{{.Text}}\r\n" Useragent string = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " + "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" DefaultAdNameTemplate string = "{{.Slug}}" ) const Usage string = `This is kleingebaeck, the kleinanzeigen.de backup tool. Usage: kleingebaeck [-dvVhmoclu] [,...] Options: -u --user Backup ads from user with uid . -d --debug Enable debug output. -v --verbose Enable verbose output. -o --outdir Set output dir (default: current directory) -l --limit Limit the ads to download to , default: load all. -c --config Use config file (default: ~/.kleingebaeck). --ignoreerrors Ignore HTTP errors, may lead to incomplete ad backup. -m --manual Show manual. -h --help Show usage. -V --version Show program version. If one or more ad listing url's are specified, only backup those, otherwise backup all ads of the given user.` type Config struct { Verbose bool `koanf:"verbose"` // loglevel=info Debug bool `koanf:"debug"` // loglevel=debug Showversion bool `koanf:"version"` // -v Showhelp bool `koanf:"help"` // -h Showmanual bool `koanf:"manual"` // -m User int `koanf:"user"` Outdir string `koanf:"outdir"` Template string `koanf:"template"` Adnametemplate string `koanf:"adnametemplate"` Loglevel string `koanf:"loglevel"` Limit int `koanf:"limit"` IgnoreErrors bool `koanf:"ignoreerrors"` Adlinks []string StatsCountAds int StatsCountImages int } func (c *Config) IncrAds() { c.StatsCountAds++ } func (c *Config) IncrImgs(num int) { c.StatsCountImages += num } // load commandline flags and config file func InitConfig(w io.Writer) (*Config, error) { var k = koanf.New(".") // determine template based on os template := DefaultTemplate if runtime.GOOS == "windows" { template = DefaultTemplateWin } // Load default values using the confmap provider. if err := k.Load(confmap.Provider(map[string]interface{}{ "template": template, "outdir": ".", "loglevel": "notice", "userid": 0, "adnametemplate": DefaultAdNameTemplate, }, "."), nil); err != nil { return nil, err } // setup custom usage f := flag.NewFlagSet("config", flag.ContinueOnError) f.Usage = func() { fmt.Fprintln(w, Usage) os.Exit(0) } // parse commandline flags f.StringP("config", "c", "", "config file") f.StringP("outdir", "o", "", "directory where to store ads") f.IntP("user", "u", 0, "user id") f.IntP("limit", "l", 0, "limit ads to be downloaded (default 0, unlimited)") f.BoolP("verbose", "v", false, "be verbose") f.BoolP("debug", "d", false, "enable debug log") f.BoolP("version", "V", false, "show program version") f.BoolP("help", "h", false, "show usage") f.BoolP("manual", "m", false, "show manual") if err := f.Parse(os.Args[1:]); err != nil { return nil, err } // generate a list of config files to try to load, including the // one provided via -c, if any var configfiles []string configfile, _ := f.GetString("config") home, _ := os.UserHomeDir() if configfile != "" { configfiles = []string{configfile} } else { configfiles = []string{ "/etc/kleingebaeck.conf", "/usr/local/etc/kleingebaeck.conf", // unix variants filepath.Join(home, ".config", "kleingebaeck", "config"), filepath.Join(home, ".kleingebaeck"), "kleingebaeck.conf", } } // Load the config file[s] for _, cfgfile := range configfiles { if path, err := os.Stat(cfgfile); !os.IsNotExist(err) { if !path.IsDir() { if err := k.Load(file.Provider(cfgfile), toml.Parser()); err != nil { return nil, errors.New("error loading config file: " + err.Error()) } } } // else: we ignore the file if it doesn't exists } // env overrides config file if err := k.Load(env.Provider("KLEINGEBAECK_", ".", func(s string) string { return strings.Replace(strings.ToLower( strings.TrimPrefix(s, "KLEINGEBAECK_")), "_", ".", -1) }), nil); err != nil { return nil, errors.New("error loading environment: " + err.Error()) } // command line overrides env if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil { return nil, errors.New("error loading flags: " + err.Error()) } // fetch values conf := &Config{} if err := k.Unmarshal("", &conf); err != nil { return nil, errors.New("error unmarshalling: " + err.Error()) } // adjust loglevel switch conf.Loglevel { case "verbose": conf.Verbose = true case "debug": conf.Debug = true } // are there any args left on commandline? if so threat them as adlinks conf.Adlinks = f.Args() return conf, nil }