Repository navigation
Fix Paper API build parsing and exact JAR selection #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '70,140p' internal/server/runner.go
rg -n 'RequiresInstall|PostDownload|FindServerJar|Filename:' internalRepository: gameap/minecraft-runner Length of output: 6614 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- runner and downloader ---'
cat -n internal/server/runner.go | sed -n '1,145p'
cat -n internal/server/downloader.go | sed -n '1,190p'
printf '%s\n' '--- provider contract and provider implementations ---'
cat -n internal/providers/provider.go | sed -n '1,100p'
for f in internal/providers/*.go; do
printf '\n--- %s ---\n' "$f"
rg -n -A28 -B8 'func .*Download|func .*PostDownload|RequiresInstall|Filename:' "$f" || true
done
printf '%s\n' '--- callers of FindServerJar and Run ---'
rg -n -C4 'FindServerJar|\.Run\(ctx|Run\(.*RunOptions|type RunOptions|Download\(' internal cmd . --glob '*.go' 2>/dev/null || trueRepository: gameap/minecraft-runner Length of output: 45296 🏁 Script executed: #!/bin/bash
set -e
cat -n internal/server/downloader.go | sed -n '115,180p'
cat -n internal/providers/forge.go | sed -n '120,190p'
rg -n -C3 'FindServerJar|RequiresInstall|PostDownload' internal --glob '*.go'Repository: gameap/minecraft-runner Length of output: 14446 Resolve the generated JAR after installation. When Forge sets 🤖 Prompt for AI Agents |
||
|
|
||
| // 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 | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '1,115p' pkg/api/paper/client.go
rg -n 'UnmarshalJSON|versions.*null|GetProject|ListGameVersions' --glob '*_test.go' --glob '*.go'Repository: gameap/minecraft-runner Length of output: 3685 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- provider callers ---'
sed -n '1,120p' internal/providers/paper.go
sed -n '1,120p' internal/providers/waterfall.go
sed -n '1,120p' internal/providers/velocity.go
printf '%s\n' '--- project/version consumers ---'
rg -n -C 4 'project\.Versions|Versions|GetProject\(' --glob '*.go' --glob '*_test.go' --glob '*.md' --glob 'go.mod'
printf '%s\n' '--- repository contract references ---'
rg -n -i -C 3 'fill v3|papermc|paper.*version|version.*paper|invalid.*version|empty.*version|nil.*version' --glob '*.go' --glob '*_test.go' --glob '*.md' --glob '*.yaml' --glob '*.yml'Repository: gameap/minecraft-runner Length of output: 50381 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
Reject If Fill v3 returns 🤖 Prompt for AI Agents |
||
| 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"` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '60,90p' pkg/api/paper/client.go
sed -n '90,135p' internal/providers/paper.go
sed -n '95,130p' internal/providers/velocity.go
sed -n '90,125p' internal/providers/waterfall.go
sed -n '1,150p' internal/server/downloader.goRepository: gameap/minecraft-runner Length of output: 8587 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
Decode the nested SHA-256 checksum. Fill v3 stores the checksum at 🤖 Prompt for AI Agents |
||
| 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 | ||
| } | ||
|
Comment on lines
+145
to
+148
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: sed -n '125,160p' pkg/api/paper/client.go
sed -n '40,75p' internal/providers/velocity.go
rg -n 'GetLatestBuild|IsStable|RECOMMENDED|STABLE' internal pkg --glob '*.go' --glob '*_test.go'Repository: gameap/minecraft-runner Length of output: 4102 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
🏁 Script executed: #!/bin/bash
set -o pipefail
printf '%s\n' '--- bound client/provider definitions ---'
rg -n -C 5 'type BuildInfo|type Client|func \(c \*Client\) GetVersionBuilds|func \(p \*VelocityProvider\) (GetLatestVersion|GetLatestBuild|ListModVersions)|GetLatestBuild\(|ListModVersions\(' pkg/api/paper internal/providers --glob '*.go'
printf '%s\n' '--- Velocity provider complete relevant sections ---'
sed -n '1,130p' internal/providers/velocity.go
printf '%s\n' '--- client build parsing and version-build request ---'
rg -n -C 8 'GetVersionBuilds|BuildInfo|json:.*channel|downloads|GetDownloadURL' pkg/api/paper --glob '*.go'
printf '%s\n' '--- direct representative Fill responses ---'
for url in \
'https://fill.papermc.io/v3/projects/velocity' \
'https://fill.papermc.io/v3/projects/velocity/versions/3.4.0/builds' \
'https://fill.papermc.io/v3/projects/velocity/versions/3.3.0/builds'; do
echo "URL $url"
curl -sS -L -H 'User-Agent: minecraft-runner-review/1.0 (https://github.com/gameap/minecraft-runner)' "$url" | python3 -c 'import json,sys; x=json.load(sys.stdin); print(json.dumps(x, indent=2)[:12000])'
doneRepository: gameap/minecraft-runner Length of output: 23556 🏁 Script executed: sed -n '1,130p' internal/providers/velocity.go; sed -n '1,180p' pkg/api/paper/client.goRepository: gameap/minecraft-runner Length of output: 7243 🌐 Web query:
💡 Result: <search_synthesis> <source_evidence> Citations:
🏁 Script executed (no clone): curl -fsSL -H 'User-Agent: minecraft-runner-review/1.0 (https://github.com/gameap/minecraft-runner)' https://fill.papermc.io/v3/projects/velocity/versions/3.4.0/builds | python3 -c 'import json,sys; x=json.load(sys.stdin); print([(b.get("id"), b.get("channel"), b.get("version")) for b in x])'Length of output: 149 🏁 Script executed (no clone): #!/bin/bash
set -o pipefail
for version in 3.5.1 3.5.0 4.0.0 4.1.1; do
url="https://fill.papermc.io/v3/projects/velocity/versions/${version}/builds"
echo "VERSION ${version}"
curl -fsSL -H 'User-Agent: minecraft-runner-review/1.0 (https://github.com/gameap/minecraft-runner)' "$url" |
python3 -c 'import json,sys; x=json.load(sys.stdin); print([(b.get("id"), b.get("channel"), b.get("version")) for b in x] if isinstance(x,list) else x)'
doneLength of output: 258 🏁 Script executed (no clone): for version in 3.5.1 3.5.0 4.0.0 4.1.1; do
echo "VERSION $version"
curl -fsSL -H 'User-Agent: minecraft-runner-review/1.0 (https://github.com/gameap/minecraft-runner)' "https://fill.papermc.io/v3/projects/velocity/versions/$version/builds" |
python3 -c 'import json,sys; x=json.load(sys.stdin); print([(b.get("id"), b.get("channel")) for b in x] if isinstance(x,list) else x)'
doneLength of output: 240 Accept both release channels for Velocity. Fill returns Use one project-aware predicate in both paths. Preserve 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // 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) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: gameap/minecraft-runner
Length of output: 4010
🏁 Script executed:
Repository: gameap/minecraft-runner
Length of output: 36943
🏁 Script executed:
Repository: gameap/minecraft-runner
Length of output: 16765
🌐 Web query:
PaperMC API v3 downloads application name Velocity Waterfall filename examples💡 Result:
<search_synthesis>
The PaperMC download service currently utilizes the "Fill" API (often referred to as v3) [1][2][3]. This service provides metadata and download links for projects including Paper, Velocity, and Waterfall [1][4]. Application Names (Project Keys) When interacting with the PaperMC API, you must use the specific project key to identify the software [1][5]. The standard keys are: - paper: The PaperMC server implementation [5] - velocity: The Velocity proxy [4] - waterfall: The Waterfall proxy [5][4] Filename Examples The filenames for these projects typically follow a pattern that includes the project name, the Minecraft version (or proxy version), and the build number [1][6]. Examples include: - Paper: paper-1.21.10-48.jar [1] - Waterfall: waterfall-258.jar [5] API Usage Notes - API Endpoint: The current service is accessed via https://fill.papermc.io/ [1]. - User-Agent: When using the API, it is required to provide a custom User-Agent header to identify your application [6][7][3]. - Data Structure: The API returns JSON data containing project versions, builds, and specific download objects that include the filename, URL, and checksums [1]. Note: While there is a "Velocity API" (e.g., v3.5.0) used for plugin development [8], this is distinct from the "Downloads API" (Fill v3) used for fetching server/proxy JAR files [1][8].
</search_synthesis>
<source_evidence>
Citations:
🏁 Script executed (no clone):
Length of output: 773
🏁 Script executed (no clone):
Length of output: 347
🏁 Script executed (no clone):
Length of output: 3158
🏁 Script executed (no clone):
Length of output: 239
🏁 Script executed:
Repository: gameap/minecraft-runner
Length of output: 1400
Ignore Velocity and Waterfall JARs. When the server directory is inside the repository, these provider-returned files can remain untracked because the current rule matches only
paper-*.jar.🤖 Prompt for AI Agents