mirror of
https://github.com/skidoodle/ncore-stats.git
synced 2025-02-15 05:09:14 +01:00
fix some stuff lol
This commit is contained in:
parent
9acfc490f0
commit
20d6510053
10 changed files with 131 additions and 30 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.git
|
||||||
|
.github
|
||||||
|
.docker-compose*
|
||||||
|
Dockerfile
|
||||||
|
*.md
|
2
.env
2
.env
|
@ -1,2 +0,0 @@
|
||||||
NICK=
|
|
||||||
PASS=
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
.env.local
|
.env**
|
||||||
|
data/**
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
FROM golang:alpine as builder
|
FROM golang:alpine AS builder
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN go build -o trackncore .
|
RUN go build -o trackncore .
|
||||||
|
|
||||||
|
@ -7,8 +9,7 @@ FROM alpine:latest
|
||||||
RUN apk --no-cache add ca-certificates
|
RUN apk --no-cache add ca-certificates
|
||||||
WORKDIR /app/
|
WORKDIR /app/
|
||||||
COPY --from=builder /build/trackncore .
|
COPY --from=builder /build/trackncore .
|
||||||
COPY --from=builder /build/.env .
|
COPY --from=builder /build/data .
|
||||||
COPY --from=builder /build/profiles.json .
|
|
||||||
COPY --from=builder /build/index.html .
|
COPY --from=builder /build/index.html .
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
CMD ["./trackncore"]
|
CMD ["./trackncore"]
|
||||||
|
|
|
@ -7,6 +7,9 @@ services:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- data:/app/data
|
- data:/app/data
|
||||||
|
environment:
|
||||||
|
- NICK=${NICK}
|
||||||
|
- PASSWORD=${PASS}
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
|
|
|
@ -7,6 +7,8 @@ services:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
volumes:
|
volumes:
|
||||||
- data:/app/data
|
- data:/app/data
|
||||||
|
environment:
|
||||||
|
- NICK=${NICK}
|
||||||
|
- PASSWORD=${PASS}
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -6,10 +6,12 @@ toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/PuerkitoBio/goquery v1.10.0
|
github.com/PuerkitoBio/goquery v1.10.0
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/andybalholm/cascadia v1.3.2 // indirect
|
github.com/andybalholm/cascadia v1.3.2 // indirect
|
||||||
golang.org/x/net v0.30.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
4
go.sum
4
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/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 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
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=
|
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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
|
128
main.go
128
main.go
|
@ -13,6 +13,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/PuerkitoBio/goquery"
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"github.com/fsnotify/fsnotify"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,7 +31,7 @@ type ProfileData struct {
|
||||||
var (
|
var (
|
||||||
profiles = map[string]string{}
|
profiles = map[string]string{}
|
||||||
jsonFile = "./data/data.json"
|
jsonFile = "./data/data.json"
|
||||||
profilesFile = "profiles.json"
|
profilesFile = "./data/profiles.json"
|
||||||
baseUrl = "https://ncore.pro/profile.php?id="
|
baseUrl = "https://ncore.pro/profile.php?id="
|
||||||
nick string
|
nick string
|
||||||
pass string
|
pass string
|
||||||
|
@ -44,46 +45,64 @@ func init() {
|
||||||
nick = os.Getenv("NICK")
|
nick = os.Getenv("NICK")
|
||||||
pass = os.Getenv("PASS")
|
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)
|
file, err := os.Open(profilesFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("Failed to open profiles file: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
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)
|
var tempProfiles map[string]string
|
||||||
json.Unmarshal(jsonBytes, &tempProfiles)
|
err = json.Unmarshal(jsonBytes, &tempProfiles)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to unmarshal profiles JSON: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
for k, v := range tempProfiles {
|
for k, v := range tempProfiles {
|
||||||
profiles[k] = baseUrl + v
|
profiles[k] = baseUrl + v
|
||||||
}
|
}
|
||||||
|
log.Println("Profiles loaded successfully.")
|
||||||
client = &http.Client{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchProfile(url string, displayName string) (*ProfileData, error) {
|
func fetchProfile(url string, displayName string) (*ProfileData, error) {
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
if err != 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))
|
req.Header.Set("Cookie", fmt.Sprintf("nick=%s; pass=%s", nick, pass))
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error fetching profile for %s: %v", displayName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
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)
|
doc, err := goquery.NewDocumentFromReader(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error parsing profile document for %s: %v", displayName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile := &ProfileData{
|
profile := &ProfileData{
|
||||||
|
@ -130,19 +149,27 @@ func fetchProfile(url string, displayName string) (*ProfileData, error) {
|
||||||
|
|
||||||
func readExistingProfiles() ([]ProfileData, error) {
|
func readExistingProfiles() ([]ProfileData, error) {
|
||||||
if _, err := os.Stat(jsonFile); os.IsNotExist(err) {
|
if _, err := os.Stat(jsonFile); os.IsNotExist(err) {
|
||||||
|
log.Printf("File %s does not exist, returning an empty profile list.", jsonFile)
|
||||||
return []ProfileData{}, nil
|
return []ProfileData{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
file, err := os.Open(jsonFile)
|
file, err := os.Open(jsonFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("error opening %s: %v", jsonFile, err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
var existingProfiles []ProfileData
|
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)
|
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 {
|
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)
|
file, err := os.OpenFile(jsonFile, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("error opening file for writing: %v", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
enc := json.NewEncoder(file)
|
enc := json.NewEncoder(file)
|
||||||
enc.SetIndent("", " ")
|
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) {
|
func dataHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
existingProfiles, err := readExistingProfiles()
|
existingProfiles, err := readExistingProfiles()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Could not read data", http.StatusInternalServerError)
|
http.Error(w, "Could not read data", http.StatusInternalServerError)
|
||||||
|
log.Printf("Error reading profiles: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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) {
|
func serveHTML(w http.ResponseWriter, r *http.Request) {
|
||||||
http.ServeFile(w, r, "index.html")
|
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() {
|
func main() {
|
||||||
ticker := time.NewTicker(time.Minute * 30)
|
ticker := time.NewTicker(time.Hour * 24)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -188,19 +271,22 @@ func main() {
|
||||||
for displayName, url := range profiles {
|
for displayName, url := range profiles {
|
||||||
profile, err := fetchProfile(url, displayName)
|
profile, err := fetchProfile(url, displayName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Printf("Error fetching profile %s: %v", displayName, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := logToJSON(profile); err != nil {
|
if err := logToJSON(profile); err != nil {
|
||||||
log.Println(err)
|
log.Printf("Error logging profile for %s: %v", displayName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<-ticker.C
|
<-ticker.C
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
go watchProfilesFile()
|
||||||
|
|
||||||
http.HandleFunc("/data", dataHandler)
|
http.HandleFunc("/data", dataHandler)
|
||||||
http.HandleFunc("/", serveHTML)
|
http.HandleFunc("/", serveHTML)
|
||||||
|
log.Println("Server is starting on port 3000...")
|
||||||
log.Fatal(http.ListenAndServe(":3000", nil))
|
log.Fatal(http.ListenAndServe(":3000", nil))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{}
|
|
Loading…
Add table
Add a link
Reference in a new issue