Skip to content

Commit e28d802

Browse files
author
Fletcher Aksel
committed
initial build
Signed-off-by: Fletcher Aksel <[email protected]>
0 parents  commit e28d802

File tree

16 files changed

+1012
-0
lines changed

16 files changed

+1012
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/gettrackers

cmd/block.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"gettrackers/internal/filter"
8+
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var blockCmd = &cobra.Command{
13+
Use: "block <pattern>",
14+
Short: "Add a pattern to the blocklist",
15+
Long: `Adds a pattern to the blocklist. If the pattern already exists, it is skipped.`,
16+
Args: cobra.ExactArgs(1),
17+
RunE: runBlock,
18+
}
19+
20+
func init() {
21+
rootCmd.AddCommand(blockCmd)
22+
}
23+
24+
func runBlock(cmd *cobra.Command, args []string) error {
25+
pattern := args[0]
26+
27+
if err := filter.AddToBlocklist(pattern); err != nil {
28+
return fmt.Errorf("failed to add to blocklist: %w", err)
29+
}
30+
31+
fmt.Fprintf(os.Stderr, "Added pattern to blocklist: %s\n", pattern)
32+
return nil
33+
}
34+

cmd/config.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"strings"
7+
8+
"gettrackers/internal/config"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var configCmd = &cobra.Command{
14+
Use: "config",
15+
Short: "Manage configuration",
16+
Long: `Get or set configuration values.`,
17+
}
18+
19+
var configGetCmd = &cobra.Command{
20+
Use: "get [key]",
21+
Short: "Show config values",
22+
Long: `Show all config values if no key is specified, or a specific value if key is given.`,
23+
Args: cobra.MaximumNArgs(1),
24+
RunE: runConfigGet,
25+
}
26+
27+
var configSetCmd = &cobra.Command{
28+
Use: "set <key> <value>",
29+
Short: "Set a config value",
30+
Long: `Set a configuration value. For source_urls, provide comma-separated URLs.`,
31+
Args: cobra.ExactArgs(2),
32+
RunE: runConfigSet,
33+
}
34+
35+
func init() {
36+
rootCmd.AddCommand(configCmd)
37+
configCmd.AddCommand(configGetCmd)
38+
configCmd.AddCommand(configSetCmd)
39+
}
40+
41+
func runConfigGet(cmd *cobra.Command, args []string) error {
42+
cfg, err := config.Load()
43+
if err != nil {
44+
return fmt.Errorf("failed to load config: %w", err)
45+
}
46+
47+
if len(args) == 0 {
48+
// Show all config
49+
fmt.Printf("source_urls:\n")
50+
for _, url := range cfg.SourceURLs {
51+
fmt.Printf(" - %s\n", url)
52+
}
53+
return nil
54+
}
55+
56+
// Show specific key
57+
key := args[0]
58+
value, err := cfg.Get(key)
59+
if err != nil {
60+
return err
61+
}
62+
63+
switch v := value.(type) {
64+
case []string:
65+
for _, item := range v {
66+
fmt.Println(item)
67+
}
68+
default:
69+
fmt.Println(value)
70+
}
71+
72+
return nil
73+
}
74+
75+
func runConfigSet(cmd *cobra.Command, args []string) error {
76+
cfg, err := config.Load()
77+
if err != nil {
78+
return fmt.Errorf("failed to load config: %w", err)
79+
}
80+
81+
key := args[0]
82+
valueStr := args[1]
83+
84+
var value interface{}
85+
switch key {
86+
case "source_urls":
87+
// Split by comma and trim spaces
88+
urls := strings.Split(valueStr, ",")
89+
trimmed := make([]string, 0, len(urls))
90+
for _, url := range urls {
91+
trimmed = append(trimmed, strings.TrimSpace(url))
92+
}
93+
value = trimmed
94+
default:
95+
value = valueStr
96+
}
97+
98+
if err := cfg.Set(key, value); err != nil {
99+
return err
100+
}
101+
102+
if err := cfg.Save(); err != nil {
103+
return fmt.Errorf("failed to save config: %w", err)
104+
}
105+
106+
fmt.Fprintf(os.Stderr, "Set %s = %v\n", key, value)
107+
return nil
108+
}
109+

cmd/fetch.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"gettrackers/internal/config"
8+
"gettrackers/internal/fetch"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var fetchCmd = &cobra.Command{
14+
Use: "fetch",
15+
Short: "Force download/update the cached sources file",
16+
Long: `Downloads tracker URLs from configured sources and updates the cache file.`,
17+
RunE: runFetch,
18+
}
19+
20+
func init() {
21+
rootCmd.AddCommand(fetchCmd)
22+
}
23+
24+
func runFetch(cmd *cobra.Command, args []string) error {
25+
// Load config
26+
cfg, err := config.Load()
27+
if err != nil {
28+
return fmt.Errorf("failed to load config: %w", err)
29+
}
30+
31+
// Force fetch
32+
if err := fetch.Fetch(cfg.SourceURLs); err != nil {
33+
return fmt.Errorf("failed to fetch: %w", err)
34+
}
35+
36+
fmt.Fprintf(os.Stderr, "Successfully updated cache\n")
37+
return nil
38+
}
39+

cmd/groups.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"os"
7+
8+
"gettrackers/internal/config"
9+
"gettrackers/internal/fetch"
10+
"gettrackers/internal/filter"
11+
"gettrackers/internal/group"
12+
13+
"github.com/spf13/cobra"
14+
)
15+
16+
var groupsCmd = &cobra.Command{
17+
Use: "groups",
18+
Short: "Output grouped tracker URLs (default command)",
19+
Long: `Downloads, filters, and outputs tracker URLs grouped by domain in random order.`,
20+
RunE: runGroups,
21+
}
22+
23+
func init() {
24+
rootCmd.AddCommand(groupsCmd)
25+
}
26+
27+
func runGroups(cmd *cobra.Command, args []string) error {
28+
// Load config
29+
cfg, err := config.Load()
30+
if err != nil {
31+
return fmt.Errorf("failed to load config: %w", err)
32+
}
33+
34+
// Check if we need to fetch
35+
shouldFetch, err := fetch.ShouldFetch(false)
36+
if err != nil {
37+
return fmt.Errorf("failed to check cache: %w", err)
38+
}
39+
40+
if shouldFetch {
41+
if err := fetch.Fetch(cfg.SourceURLs); err != nil {
42+
// Try to use stale cache if available
43+
fmt.Fprintf(os.Stderr, "Warning: failed to fetch new data: %v\n", err)
44+
fmt.Fprintf(os.Stderr, "Attempting to use stale cache...\n")
45+
}
46+
}
47+
48+
// Load cache
49+
urls, err := fetch.LoadCache()
50+
if err != nil {
51+
return fmt.Errorf("failed to load cache: %w", err)
52+
}
53+
54+
// Load blocklist
55+
blocklist, err := filter.LoadBlocklist()
56+
if err != nil {
57+
return fmt.Errorf("failed to load blocklist: %w", err)
58+
}
59+
60+
// Filter URLs
61+
filtered := filter.Filter(urls, blocklist)
62+
63+
if len(filtered) == 0 {
64+
if len(urls) > 0 {
65+
fmt.Fprintf(os.Stderr, "all urls blocked\n")
66+
}
67+
return nil
68+
}
69+
70+
// Group by domain
71+
groups := group.GroupByDomain(filtered)
72+
73+
// Get output writer
74+
writer, err := getOutputWriter()
75+
if err != nil {
76+
return err
77+
}
78+
defer func() {
79+
if writer != os.Stdout {
80+
writer.Close()
81+
}
82+
}()
83+
84+
// Output groups
85+
return outputGroups(writer, groups)
86+
}
87+
88+
func outputGroups(writer io.Writer, groups []group.Group) error {
89+
for i, g := range groups {
90+
if i > 0 {
91+
if _, err := fmt.Fprintln(writer); err != nil {
92+
return err
93+
}
94+
}
95+
for _, url := range g.URLs {
96+
if _, err := fmt.Fprintln(writer, url); err != nil {
97+
return err
98+
}
99+
}
100+
}
101+
return nil
102+
}
103+

cmd/root.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var rootCmd = &cobra.Command{
11+
Use: "gettrackers",
12+
Short: "Download, filter, and output tracker URLs grouped by domain",
13+
Long: `gettrackers is a CLI tool that downloads tracker URLs from configurable sources, filters them using a blocklist, and outputs them grouped by domain.`,
14+
RunE: runGroups,
15+
}
16+
17+
var outputFile string
18+
19+
func init() {
20+
rootCmd.PersistentFlags().StringVarP(&outputFile, "output", "o", "", "Write output to file instead of stdout")
21+
}
22+
23+
// Execute runs the root command
24+
func Execute() error {
25+
return rootCmd.Execute()
26+
}
27+
28+
// getOutputWriter returns the appropriate writer for output
29+
func getOutputWriter() (*os.File, error) {
30+
if outputFile != "" {
31+
file, err := os.Create(outputFile)
32+
if err != nil {
33+
return nil, fmt.Errorf("failed to create output file: %w", err)
34+
}
35+
return file, nil
36+
}
37+
return os.Stdout, nil
38+
}
39+

cmd/show.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"gettrackers/internal/fetch"
8+
"gettrackers/internal/filter"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
var showCmd = &cobra.Command{
14+
Use: "show",
15+
Short: "Show cached sources or blocklist",
16+
Long: `Display the cached source URLs or blocklist entries.`,
17+
}
18+
19+
var showSourcesCmd = &cobra.Command{
20+
Use: "sources",
21+
Short: "Display the cached source URLs",
22+
RunE: runShowSources,
23+
}
24+
25+
var showBlocklistCmd = &cobra.Command{
26+
Use: "blocklist",
27+
Short: "Display blocklist entries",
28+
RunE: runShowBlocklist,
29+
}
30+
31+
func init() {
32+
rootCmd.AddCommand(showCmd)
33+
showCmd.AddCommand(showSourcesCmd)
34+
showCmd.AddCommand(showBlocklistCmd)
35+
}
36+
37+
func runShowSources(cmd *cobra.Command, args []string) error {
38+
urls, err := fetch.LoadCache()
39+
if err != nil {
40+
return fmt.Errorf("failed to load cache: %w", err)
41+
}
42+
43+
for _, url := range urls {
44+
fmt.Println(url)
45+
}
46+
47+
return nil
48+
}
49+
50+
func runShowBlocklist(cmd *cobra.Command, args []string) error {
51+
blocklist, err := filter.LoadBlocklist()
52+
if err != nil {
53+
return fmt.Errorf("failed to load blocklist: %w", err)
54+
}
55+
56+
if len(blocklist) == 0 {
57+
fmt.Fprintf(os.Stderr, "Blocklist is empty\n")
58+
return nil
59+
}
60+
61+
for _, pattern := range blocklist {
62+
fmt.Println(pattern)
63+
}
64+
65+
return nil
66+
}
67+

0 commit comments

Comments
 (0)