diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..62d3d8e --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +.paper/ +banned-ips.json +banned-players.json +bukkit.yml +cache/ +commands.yml +config/ +eula.txt +help.yml +libraries/ +logs/ +mcrun +ops.json +paper-*.jar +permissions.yml +plugins/ +server.properties +spigot.yml +usercache.json +versions/ +whitelist.json +world/ +*.bak diff --git a/internal/providers/paper.go b/internal/providers/paper.go index 7c5ee90..ed52bd6 100644 --- a/internal/providers/paper.go +++ b/internal/providers/paper.go @@ -51,12 +51,12 @@ func (p *PaperProvider) ListModVersions(ctx context.Context, mcVersion string) ( return nil, err } - versions := make([]VersionInfo, 0, len(vb.Builds)) - for _, build := range vb.Builds { + versions := make([]VersionInfo, 0, len(*vb)) + for _, build := range *vb { versions = append(versions, VersionInfo{ MinecraftVersion: mcVersion, - ModVersion: strconv.Itoa(build), - IsStable: true, + ModVersion: strconv.Itoa(build.ID), + IsStable: build.Channel == "STABLE", Type: "build", }) } @@ -106,11 +106,11 @@ func (p *PaperProvider) GetServerJar(ctx context.Context, mcVersion, modVersion return nil, err } - downloadURL := p.client.GetDownloadURL("paper", mcVersion, buildInfo.Build, buildInfo.Downloads.Application.Name) + downloadURL := buildInfo.Downloads.Application.URL return &ServerJar{ Version: mcVersion, - ModVersion: strconv.Itoa(buildInfo.Build), + ModVersion: strconv.Itoa(buildInfo.ID), URL: downloadURL, SHA256: buildInfo.Downloads.Application.SHA256, Filename: buildInfo.Downloads.Application.Name, diff --git a/internal/providers/velocity.go b/internal/providers/velocity.go index 2c49c0c..63b92eb 100644 --- a/internal/providers/velocity.go +++ b/internal/providers/velocity.go @@ -51,12 +51,12 @@ func (p *VelocityProvider) ListModVersions(ctx context.Context, version string) return nil, err } - versions := make([]VersionInfo, 0, len(vb.Builds)) - for _, build := range vb.Builds { + versions := make([]VersionInfo, 0, len(*vb)) + for _, build := range *vb { versions = append(versions, VersionInfo{ MinecraftVersion: version, - ModVersion: strconv.Itoa(build), - IsStable: true, + ModVersion: strconv.Itoa(build.ID), + IsStable: build.Channel == "STABLE", Type: "build", }) } @@ -103,11 +103,16 @@ func (p *VelocityProvider) GetServerJar(ctx context.Context, version, modVersion return nil, err } - downloadURL := p.client.GetDownloadURL("velocity", version, buildInfo.Build, buildInfo.Downloads.Application.Name) + downloadURL := buildInfo.Downloads.Application.URL + + buildNumber := buildInfo.ID + if buildNumber == 0 { + buildNumber = buildInfo.Build + } return &ServerJar{ Version: version, - ModVersion: strconv.Itoa(buildInfo.Build), + ModVersion: strconv.Itoa(buildNumber), URL: downloadURL, SHA256: buildInfo.Downloads.Application.SHA256, Filename: buildInfo.Downloads.Application.Name, @@ -122,5 +127,5 @@ func (p *VelocityProvider) PostDownload(ctx context.Context, jarPath string, jav // GetRecommendedJavaVersion returns Java 17 for modern Velocity func (p *VelocityProvider) GetRecommendedJavaVersion(_ context.Context, _ string) int { - return 17 + return 25 } diff --git a/internal/providers/waterfall.go b/internal/providers/waterfall.go index 01122ec..4ebde91 100644 --- a/internal/providers/waterfall.go +++ b/internal/providers/waterfall.go @@ -51,12 +51,12 @@ func (p *WaterfallProvider) ListModVersions(ctx context.Context, version string) return nil, err } - versions := make([]VersionInfo, 0, len(vb.Builds)) - for _, build := range vb.Builds { + versions := make([]VersionInfo, 0, len(*vb)) + for _, build := range *vb { versions = append(versions, VersionInfo{ MinecraftVersion: version, - ModVersion: strconv.Itoa(build), - IsStable: true, + ModVersion: strconv.Itoa(build.ID), + IsStable: build.Channel == "STABLE", Type: "build", }) } diff --git a/internal/server/runner.go b/internal/server/runner.go index b2a0154..1b8b214 100644 --- a/internal/server/runner.go +++ b/internal/server/runner.go @@ -110,11 +110,8 @@ func (r *Runner) Run(ctx context.Context, opts RunOptions) error { } } - // Find the server JAR to run - serverJar, err := FindServerJar(opts.Directory, opts.Mod, opts.Version) - if err != nil { - return fmt.Errorf("failed to find server JAR: %w", err) - } + // Use the exact JAR returned by the downloader. + serverJar := filepath.Join(opts.Directory, jar.Filename) // Build and run command args := r.buildJVMArgs(opts, serverJar) @@ -207,8 +204,12 @@ func (r *Runner) buildJVMArgs(opts RunOptions, jarPath string) []string { // Add custom JVM args args = append(args, opts.JVMArgs...) - // JAR and nogui - args = append(args, "-jar", jarPath, "--nogui") + // JAR and server-specific arguments + args = append(args, "-jar", jarPath) + + if opts.Mod != "velocity" { + args = append(args, "--nogui") + } return args } diff --git a/pkg/api/paper/client.go b/pkg/api/paper/client.go index 7ad1081..40acdae 100644 --- a/pkg/api/paper/client.go +++ b/pkg/api/paper/client.go @@ -8,54 +8,80 @@ import ( "github.com/gameap/minecraft-runner/internal/utils" ) -const ( - baseURL = "https://api.papermc.io/v2" -) +const baseURL = "https://fill.papermc.io/v3" -// Client is the Paper API client type Client struct { http *utils.HTTPClient } -// NewClient creates a new Paper API client func NewClient() *Client { return &Client{ http: utils.NewHTTPClient(false), } } -// Project represents a Paper project type Project struct { ProjectID string `json:"project_id"` ProjectName string `json:"project_name"` Versions []string `json:"versions"` } -// VersionBuilds contains builds for a specific version -type VersionBuilds struct { - ProjectID string `json:"project_id"` - Version string `json:"version"` - Builds []int `json:"builds"` +func (p *Project) UnmarshalJSON(data []byte) error { + var raw struct { + ProjectID string `json:"project_id"` + ProjectName string `json:"project_name"` + Versions json.RawMessage `json:"versions"` + } + + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + p.ProjectID = raw.ProjectID + p.ProjectName = raw.ProjectName + + // Paper returns versions as a simple array. + var versions []string + if err := json.Unmarshal(raw.Versions, &versions); err == nil { + p.Versions = versions + return nil + } + + // Velocity returns versions grouped into objects. + var groups map[string][]string + if err := json.Unmarshal(raw.Versions, &groups); err != nil { + return fmt.Errorf("invalid versions format: %w", err) + } + + for _, group := range groups { + p.Versions = append(p.Versions, group...) + } + + return nil } -// BuildInfo contains detailed information about a build +type VersionBuilds []BuildInfo + type BuildInfo struct { ProjectID string `json:"project_id"` Version string `json:"version"` Build int `json:"build"` + ID int `json:"id"` Time string `json:"time"` Channel string `json:"channel"` Downloads struct { Application struct { Name string `json:"name"` SHA256 string `json:"sha256"` - } `json:"application"` + URL string `json:"url"` + Size int64 `json:"size"` + } `json:"server:default"` } `json:"downloads"` } -// GetProject fetches project information func (c *Client) GetProject(ctx context.Context, project string) (*Project, error) { url := fmt.Sprintf("%s/projects/%s", baseURL, project) + data, err := c.http.Get(ctx, url) if err != nil { return nil, fmt.Errorf("failed to fetch project: %w", err) @@ -69,9 +95,9 @@ func (c *Client) GetProject(ctx context.Context, project string) (*Project, erro return &p, nil } -// GetVersionBuilds fetches available builds for a version func (c *Client) GetVersionBuilds(ctx context.Context, project, version string) (*VersionBuilds, error) { - url := fmt.Sprintf("%s/projects/%s/versions/%s", baseURL, project, version) + url := fmt.Sprintf("%s/projects/%s/versions/%s/builds", baseURL, project, version) + data, err := c.http.Get(ctx, url) if err != nil { return nil, fmt.Errorf("failed to fetch version builds: %w", err) @@ -85,41 +111,42 @@ func (c *Client) GetVersionBuilds(ctx context.Context, project, version string) return &vb, nil } -// GetBuildInfo fetches information about a specific build func (c *Client) GetBuildInfo(ctx context.Context, project, version string, build int) (*BuildInfo, error) { - url := fmt.Sprintf("%s/projects/%s/versions/%s/builds/%d", baseURL, project, version, build) - data, err := c.http.Get(ctx, url) + vb, err := c.GetVersionBuilds(ctx, project, version) if err != nil { - return nil, fmt.Errorf("failed to fetch build info: %w", err) + return nil, err } - var bi BuildInfo - if err := json.Unmarshal(data, &bi); err != nil { - return nil, fmt.Errorf("failed to parse build info: %w", err) + for _, b := range *vb { + if b.Build == build || b.ID == build { + return &b, nil + } } - return &bi, nil + return nil, fmt.Errorf("build %d not found for version %s", build, version) } -// GetDownloadURL constructs the download URL for a build func (c *Client) GetDownloadURL(project, version string, build int, filename string) string { - return fmt.Sprintf("%s/projects/%s/versions/%s/builds/%d/downloads/%s", - baseURL, project, version, build, filename) + return fmt.Sprintf( + "https://fill.papermc.io/v3/projects/%s/versions/%s/builds/%d/downloads/%s", + project, + version, + build, + filename, + ) } -// GetLatestBuild gets the latest build for a version func (c *Client) GetLatestBuild(ctx context.Context, project, version string) (*BuildInfo, error) { vb, err := c.GetVersionBuilds(ctx, project, version) if err != nil { return nil, err } - if len(vb.Builds) == 0 { - return nil, fmt.Errorf("no builds available for version %s", version) + for _, build := range *vb { + if build.Channel == "STABLE" { + return &build, nil + } } - // Get the latest build (last in the list) - latestBuild := vb.Builds[len(vb.Builds)-1] - - return c.GetBuildInfo(ctx, project, version, latestBuild) + return nil, fmt.Errorf("no stable builds available for version %s", version) }