diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e2d7f85 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.git +.github +.docker-compose* +Dockerfile +*.md diff --git a/.env b/.env deleted file mode 100644 index 35cc35d..0000000 --- a/.env +++ /dev/null @@ -1,2 +0,0 @@ -NICK= -PASS= diff --git a/.gitignore b/.gitignore index 11ee758..d0f7eee 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.env.local +.env** +data/** diff --git a/Dockerfile b/Dockerfile index f676ff1..d75dabc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,7 @@ -FROM golang:alpine as builder +FROM golang:alpine AS builder WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download COPY . . RUN go build -o trackncore . @@ -7,8 +9,7 @@ FROM alpine:latest RUN apk --no-cache add ca-certificates WORKDIR /app/ COPY --from=builder /build/trackncore . -COPY --from=builder /build/.env . -COPY --from=builder /build/profiles.json . +COPY --from=builder /build/data . COPY --from=builder /build/index.html . EXPOSE 3000 CMD ["./trackncore"] diff --git a/docker-compose.dev.yaml b/docker-compose.dev.yaml index 3a451f9..a01f5db 100644 --- a/docker-compose.dev.yaml +++ b/docker-compose.dev.yaml @@ -7,6 +7,9 @@ services: - "3000:3000" volumes: - data:/app/data + environment: + - NICK=${NICK} + - PASSWORD=${PASS} volumes: data: diff --git a/docker-compose.yaml b/docker-compose.yaml index 1f6569e..b53e5bc 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -7,6 +7,8 @@ services: - "3000:3000" volumes: - data:/app/data - + environment: + - NICK=${NICK} + - PASSWORD=${PASS} volumes: data: diff --git a/go.mod b/go.mod index e3b5421..86a5e14 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,12 @@ toolchain go1.23.2 require ( github.com/PuerkitoBio/goquery v1.10.0 + github.com/fsnotify/fsnotify v1.7.0 github.com/joho/godotenv v1.5.1 ) require ( github.com/andybalholm/cascadia v1.3.2 // indirect golang.org/x/net v0.30.0 // indirect + golang.org/x/sys v0.26.0 // indirect ) diff --git a/go.sum b/go.sum index 367fe40..169de5b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ github.com/PuerkitoBio/goquery v1.10.0 h1:6fiXdLuUvYs2OJSvNRqlNPoBm6YABE226xrbav github.com/PuerkitoBio/goquery v1.10.0/go.mod h1:TjZZl68Q3eGHNBA8CWaxAN7rOU1EbDz3CWuolcO5Yu4= github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss= github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= @@ -26,6 +28,8 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= +golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= diff --git a/main.go b/main.go index cd2a5a5..d4bcc84 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,7 @@ import ( "time" "github.com/PuerkitoBio/goquery" + "github.com/fsnotify/fsnotify" "github.com/joho/godotenv" ) @@ -24,13 +25,13 @@ type ProfileData struct { CurrentUpload string `json:"current_upload"` CurrentDownload string `json:"current_download"` Points int `json:"points"` - SeedingCount int `json:"seeding_count"` + SeedingCount int `json:"seeding_count"` } var ( profiles = map[string]string{} jsonFile = "./data/data.json" - profilesFile = "profiles.json" + profilesFile = "./data/profiles.json" baseUrl = "https://ncore.pro/profile.php?id=" nick string pass string @@ -44,46 +45,64 @@ func init() { nick = os.Getenv("NICK") pass = os.Getenv("PASS") + if _, err := os.Stat(profilesFile); os.IsNotExist(err) { + log.Printf("File %s does not exist, creating an empty one", profilesFile) + err := os.WriteFile(profilesFile, []byte("{}"), 0644) + if err != nil { + log.Fatalf("Failed to create profiles file: %v", err) + } + } + + loadProfiles() + + client = &http.Client{} +} + +func loadProfiles() { file, err := os.Open(profilesFile) if err != nil { - log.Fatal(err) + log.Fatalf("Failed to open profiles file: %v", err) } defer file.Close() - jsonBytes, _ := io.ReadAll(file) + jsonBytes, err := io.ReadAll(file) + if err != nil { + log.Fatalf("Failed to read profiles file: %v", err) + } - var tempProfiles map[string]string = make(map[string]string) - json.Unmarshal(jsonBytes, &tempProfiles) + var tempProfiles map[string]string + err = json.Unmarshal(jsonBytes, &tempProfiles) + if err != nil { + log.Fatalf("Failed to unmarshal profiles JSON: %v", err) + } for k, v := range tempProfiles { profiles[k] = baseUrl + v } - - client = &http.Client{} + log.Println("Profiles loaded successfully.") } func fetchProfile(url string, displayName string) (*ProfileData, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("error creating request for %s: %v", displayName, err) } req.Header.Set("Cookie", fmt.Sprintf("nick=%s; pass=%s", nick, pass)) resp, err := client.Do(req) if err != nil { - return nil, err + return nil, fmt.Errorf("error fetching profile for %s: %v", displayName, err) } - defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to fetch profile: %s", displayName) + return nil, fmt.Errorf("failed to fetch profile %s: received status %d", displayName, resp.StatusCode) } doc, err := goquery.NewDocumentFromReader(resp.Body) if err != nil { - return nil, err + return nil, fmt.Errorf("error parsing profile document for %s: %v", displayName, err) } profile := &ProfileData{ @@ -130,19 +149,27 @@ func fetchProfile(url string, displayName string) (*ProfileData, error) { func readExistingProfiles() ([]ProfileData, error) { if _, err := os.Stat(jsonFile); os.IsNotExist(err) { + log.Printf("File %s does not exist, returning an empty profile list.", jsonFile) return []ProfileData{}, nil } file, err := os.Open(jsonFile) if err != nil { - return nil, err + return nil, fmt.Errorf("error opening %s: %v", jsonFile, err) } defer file.Close() var existingProfiles []ProfileData - byteValue, _ := io.ReadAll(file) + byteValue, err := io.ReadAll(file) + if err != nil { + return nil, fmt.Errorf("error reading %s: %v", jsonFile, err) + } + err = json.Unmarshal(byteValue, &existingProfiles) - return existingProfiles, err + if err != nil { + return nil, fmt.Errorf("error unmarshalling profile data: %v", err) + } + return existingProfiles, nil } func logToJSON(profile *ProfileData) error { @@ -155,32 +182,88 @@ func logToJSON(profile *ProfileData) error { file, err := os.OpenFile(jsonFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { - return err + return fmt.Errorf("error opening file for writing: %v", err) } defer file.Close() enc := json.NewEncoder(file) enc.SetIndent("", " ") - return enc.Encode(existingProfiles) + err = enc.Encode(existingProfiles) + if err != nil { + return fmt.Errorf("error encoding JSON data: %v", err) + } + log.Printf("Profile for %s logged successfully.", profile.Owner) + return nil } func dataHandler(w http.ResponseWriter, r *http.Request) { existingProfiles, err := readExistingProfiles() if err != nil { http.Error(w, "Could not read data", http.StatusInternalServerError) + log.Printf("Error reading profiles: %v", err) return } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(existingProfiles) + err = json.NewEncoder(w).Encode(existingProfiles) + if err != nil { + log.Printf("Error encoding profiles to JSON: %v", err) + } } func serveHTML(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "index.html") } +func watchProfilesFile() { + watcher, err := fsnotify.NewWatcher() + if err != nil { + log.Fatalf("Error creating file watcher: %v", err) + } + defer watcher.Close() + + err = watcher.Add(profilesFile) + if err != nil { + log.Fatalf("Error adding file to watcher: %v", err) + } + + debounce := time.AfterFunc(0, func() {}) + + for { + select { + case event, ok := <-watcher.Events: + if !ok { + return + } + if event.Op&fsnotify.Write == fsnotify.Write || event.Op&fsnotify.Create == fsnotify.Create { + log.Println("Profiles file changed, reloading profiles...") + debounce.Stop() // Stop any running debounce timer + debounce = time.AfterFunc(500*time.Millisecond, func() { + loadProfiles() + for displayName, url := range profiles { + profile, err := fetchProfile(url, displayName) + if err != nil { + log.Printf("Error fetching profile for %s after file change: %v", displayName, err) + continue + } + + if err := logToJSON(profile); err != nil { + log.Printf("Error logging profile for %s: %v", displayName, err) + } + } + }) + } + case err, ok := <-watcher.Errors: + if !ok { + return + } + log.Printf("File watcher error: %v", err) + } + } +} + func main() { - ticker := time.NewTicker(time.Minute * 30) + ticker := time.NewTicker(time.Hour * 24) defer ticker.Stop() go func() { @@ -188,19 +271,22 @@ func main() { for displayName, url := range profiles { profile, err := fetchProfile(url, displayName) if err != nil { - log.Println(err) + log.Printf("Error fetching profile %s: %v", displayName, err) continue } if err := logToJSON(profile); err != nil { - log.Println(err) + log.Printf("Error logging profile for %s: %v", displayName, err) } } <-ticker.C } }() + go watchProfilesFile() + http.HandleFunc("/data", dataHandler) http.HandleFunc("/", serveHTML) + log.Println("Server is starting on port 3000...") log.Fatal(http.ListenAndServe(":3000", nil)) } diff --git a/profiles.json b/profiles.json deleted file mode 100644 index 0967ef4..0000000 --- a/profiles.json +++ /dev/null @@ -1 +0,0 @@ -{}