hachem.wtf
aboutsummaryrefslogtreecommitdiff
path: root/internal/modrinth
diff options
context:
space:
mode:
authorhachem <im@hachem.wtf>2026-08-29 03:29:51 +0200
committerhachem <im@hachem.wtf>2026-08-29 03:29:51 +0200
commita221b4c5edd3ff4d4a9d74113d9d3b8c7473133d (patch)
treed902da32c84dad3d5fe0152be07d3bea6d4f9ee1 /internal/modrinth
parentf60177934a18daea3b24a37cf109bb2c7bfaaa72 (diff)
add: modrinth client and check command
Diffstat (limited to 'internal/modrinth')
-rw-r--r--internal/modrinth/modrinth.go128
1 files changed, 128 insertions, 0 deletions
diff --git a/internal/modrinth/modrinth.go b/internal/modrinth/modrinth.go
new file mode 100644
index 0000000..8060f3b
--- /dev/null
+++ b/internal/modrinth/modrinth.go
@@ -0,0 +1,128 @@
+package modrinth
+
+import (
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "time"
+)
+
+const (
+ apiBase = "https://api.modrinth.com/v2"
+ userAgent = "benson/2.0 (github.com/hachem-wtf/benson)"
+)
+
+type File struct {
+ URL string `json:"url"`
+ Filename string `json:"filename"`
+ Primary bool `json:"primary"`
+}
+
+type Version struct {
+ ID string `json:"id"`
+ VersionNumber string `json:"version_number"`
+ DatePublished time.Time `json:"date_published"`
+ GameVersions []string `json:"game_versions"`
+ Loaders []string `json:"loaders"`
+ Files []File `json:"files"`
+}
+
+type Client struct {
+ http *http.Client
+}
+
+func NewClient() *Client {
+ return &Client{http: &http.Client{Timeout: 30 * time.Second}}
+}
+
+func (c *Client) GetVersions(slug string) ([]Version, error) {
+ url := fmt.Sprintf("%s/project/%s/version", apiBase, slug)
+
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("User-Agent", userAgent)
+
+ resp, err := c.http.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("modrinth: %s returned %s", slug, resp.Status)
+ }
+
+ var versions []Version
+ if err := json.NewDecoder(resp.Body).Decode(&versions); err != nil {
+ return nil, err
+ }
+ return versions, nil
+}
+
+func LatestVersion(versions []Version) *Version {
+ if len(versions) == 0 {
+ return nil
+ }
+
+ best := 0
+ for i := 1; i < len(versions); i++ {
+ if versions[i].DatePublished.After(versions[best].DatePublished) {
+ best = i
+ }
+ }
+ return &versions[best]
+}
+
+func CompatibleVersion(versions []Version, mcVersion, loader string) *Version {
+ var compatible []Version
+ for _, v := range versions {
+ if !contains(v.GameVersions, mcVersion) {
+ continue
+ }
+ if loader != "" && !contains(v.Loaders, loader) {
+ continue
+ }
+ compatible = append(compatible, v)
+ }
+ return LatestVersion(compatible)
+}
+
+func (c *Client) Download(url, dest string) error {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ return err
+ }
+ req.Header.Set("User-Agent", userAgent)
+
+ resp, err := c.http.Do(req)
+ if err != nil {
+ return err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("download: %s returned %s", url, resp.Status)
+ }
+
+ f, err := os.Create(dest)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ _, err = io.Copy(f, resp.Body)
+ return err
+}
+
+func contains(list []string, s string) bool {
+ for _, item := range list {
+ if item == s {
+ return true
+ }
+ }
+ return false
+}