Files
IPAGo/main.go
trojvn f53e327ec2 feat(client): add IPAGo client implementation
Add a new Go client implementation for interacting with the IPAGo server. The client includes functionality for authentication, searching for apps, and downloading apps. The client is structured to handle errors and retries, and includes progress reporting for downloads. The client is added to the .gitignore file and the README.md file is updated with installation instructions. The server-side code is also updated to include the Content-Length header for download progress reporting.
2026-03-10 05:33:41 +03:00

112 lines
2.7 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"ipa/IPAGo/models"
"ipa/IPAGo/wrapper"
"net/http"
"os"
"path/filepath"
"time"
"github.com/danielgtaylor/huma/v2"
"github.com/danielgtaylor/huma/v2/adapters/humago"
)
const (
version = "1.0.1"
)
func main() {
port := flag.Int("port", 8888, "Port to listen on")
flag.Parse()
mux := http.NewServeMux()
config := huma.DefaultConfig("IPAGo", version)
config.CreateHooks = nil
api := humago.New(mux, config)
huma.Get(
api, "/auth/login",
func(ctx context.Context, input *models.AuthInput) (*models.AuthOutput, error) {
output := &models.AuthOutput{}
output.Body.Success = wrapper.Auth(input.User, input.Pswd, input.Code)
return output, nil
})
huma.Get(
api, "/auth/info",
func(ctx context.Context, input *models.AuthInfoInput) (*models.AuthInfoOutput, error) {
output := &models.AuthInfoOutput{}
output.Body.Success = wrapper.AuthInfo(input.User, input.Pswd)
return output, nil
})
huma.Get(
api, "/search",
func(ctx context.Context, input *models.SearchInput) (*models.SearchOutput, error) {
output := &models.SearchOutput{}
output.Body.Apps = []wrapper.App{}
output.Body.Success = false
if input.Limit == 0 {
input.Limit = 1
}
if input.Query == "" {
return output, nil
}
apps, err := wrapper.Search(input.Query, input.Limit)
if err != nil {
return output, nil
}
output.Body.Success = true
output.Body.Apps = apps
return output, nil
})
huma.Get(
api, "/download",
func(ctx context.Context, input *models.DownloadInput) (*models.DownloadOutput, error) {
outputPath := filepath.Join(os.TempDir(), fmt.Sprintf("%s.ipa", time.Now().Format("20060102150405")))
output := &models.DownloadOutput{}
if input.BundleID == "" {
return nil, errors.New("bundleID is empty")
}
success := wrapper.Download(input.BundleID, outputPath)
if !success {
return nil, errors.New("failed to download")
}
output.ContentDisposition = fmt.Sprintf("attachment; filename=%s", filepath.Base(outputPath))
output.ContentType = "application/octet-stream"
output.Body = func(hctx huma.Context) {
file, err := os.Open(outputPath)
if err != nil {
return
}
defer func() {
file.Close()
os.Remove(outputPath)
fmt.Println("File removed after download")
}()
// Set Content-Length header so the client can show progress
if info, err := file.Stat(); err == nil {
hctx.SetHeader("Content-Length", fmt.Sprintf("%d", info.Size()))
}
_, _ = io.Copy(hctx.BodyWriter(), file)
}
return output, nil
})
addr := fmt.Sprintf(":%d", *port)
fmt.Printf("Server is running on %s\n", addr)
http.ListenAndServe(addr, mux)
}