From a10907f1cc211a668e9c0f721f9e8282f44dc0b9 Mon Sep 17 00:00:00 2001 From: CosmicPredator Date: Sat, 21 Jun 2025 19:25:10 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20profile=20command=20now=20renders?= =?UTF-8?q?=20user=20avatar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/api/queries.go | 3 + internal/api/responses/user_profile.go | 9 +- internal/ui/image_renderer.go | 121 +++++++++++++++++++++++++ internal/ui/profile.go | 12 ++- internal/viewmodel/profile_handler.go | 3 +- 5 files changed, 142 insertions(+), 6 deletions(-) create mode 100644 internal/ui/image_renderer.go diff --git a/internal/api/queries.go b/internal/api/queries.go index a695915..8765493 100644 --- a/internal/api/queries.go +++ b/internal/api/queries.go @@ -55,6 +55,9 @@ const viewerQuery = `query { Viewer { id name + avatar { + large + } statistics { anime { count diff --git a/internal/api/responses/user_profile.go b/internal/api/responses/user_profile.go index 5d142c6..ba8e1e4 100644 --- a/internal/api/responses/user_profile.go +++ b/internal/api/responses/user_profile.go @@ -3,9 +3,12 @@ package responses type Profile struct { Data struct { Viewer struct { - Name string `json:"name"` - SiteUrl string `json:"siteUrl"` - Id int `json:"id"` + Name string `json:"name"` + SiteUrl string `json:"siteUrl"` + Id int `json:"id"` + Avatar struct { + Large string `json:"large"` + } `json:"avatar"` Statistics struct { Anime struct { Count int `json:"count"` diff --git a/internal/ui/image_renderer.go b/internal/ui/image_renderer.go new file mode 100644 index 0000000..462bee6 --- /dev/null +++ b/internal/ui/image_renderer.go @@ -0,0 +1,121 @@ +package ui + +import ( + "bytes" + "encoding/base64" + "fmt" + "image" + _ "image/jpeg" + "image/png" + "io" + "net/http" + "os" +) + +func convertToPNG(jpgData []byte) ([]byte, error) { + img, _, err := image.Decode(bytes.NewReader(jpgData)) + //resized := resize.Resize(220, 300, img, resize.Lanczos3) + + if err != nil { + return nil, err + } + var buf bytes.Buffer + err = png.Encode(&buf, img) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func serializeGRCommand(payload []byte, cmd map[string]string) []byte { + seq := "\033_G" + first := true + for k, v := range cmd { + if !first { + seq += "," + } + first = false + seq += fmt.Sprintf("%s=%s", k, v) + } + if payload != nil { + seq += ";" + } + result := []byte(seq) + if payload != nil { + result = append(result, payload...) + } + result = append(result, []byte("\033\\")...) + return result +} + + +func writeChunked(cmd map[string]string, data []byte) { + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(data))) + base64.StdEncoding.Encode(encoded, data) + + chunkSize := 4096 + for len(encoded) > 0 { + end := chunkSize + if end > len(encoded) { + end = len(encoded) + } + chunk := encoded[:end] + encoded = encoded[end:] + + cmdCopy := make(map[string]string) + for k, v := range cmd { + cmdCopy[k] = v + } + if len(encoded) > 0 { + cmdCopy["m"] = "1" + } else { + cmdCopy["m"] = "0" + } + + serialized := serializeGRCommand(chunk, cmdCopy) + os.Stdout.Write(serialized) + os.Stdout.Sync() + } + fmt.Println("") +} + +type KGPParams struct { + R string + C string +} + +func RenderWithImage(imageUrl string, content string, kgpParams KGPParams, numLines int) error { + resp, err := http.Get(imageUrl) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + imageBytes, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + pngData, err := convertToPNG(imageBytes) + if err != nil { + panic(err) + } + + cmd := map[string]string{ + "a": "T", + "f": "100", + "x": "10", + "r": kgpParams.R, + "c": kgpParams.C, + "z": "1", + } + + fmt.Print("\n") + fmt.Println(content) + fmt.Printf("\033[%dA\033[2C", numLines) + + writeChunked(cmd, pngData) + fmt.Print("\n") + + return nil +} \ No newline at end of file diff --git a/internal/ui/profile.go b/internal/ui/profile.go index c129e9d..de633a0 100644 --- a/internal/ui/profile.go +++ b/internal/ui/profile.go @@ -15,6 +15,7 @@ type ProfileUI struct { TotalManga int MinutesWatched int ChaptersRead int + AvatarUrl string SiteUrl string } @@ -59,7 +60,8 @@ func (p *ProfileUI) Render() error { for _, kv := range dataSlice { sb.WriteString( fmt.Sprintf( - "%s : %s\n", + "%*s%s : %s\n", + 20, "", keyStyle.MarginRight(maxKeyLen-len(kv.Key)).Render(kv.Key), valueStyle.Render(kv.Value), ), @@ -67,6 +69,12 @@ func (p *ProfileUI) Render() error { } // Display the output - fmt.Print(sb.String()) + RenderWithImage( + p.AvatarUrl, + sb.String(), + KGPParams{ + R: "7", + C: "15", + },8) return nil } diff --git a/internal/viewmodel/profile_handler.go b/internal/viewmodel/profile_handler.go index aae4a30..b6e9138 100644 --- a/internal/viewmodel/profile_handler.go +++ b/internal/viewmodel/profile_handler.go @@ -8,7 +8,7 @@ import ( "github.com/CosmicPredator/chibi/internal/ui" ) -// get's user profile information form API and +// get's user profile information form API and // displays it func HandleProfile() error { var profile *responses.Profile @@ -32,6 +32,7 @@ func HandleProfile() error { MinutesWatched: profile.Data.Viewer.Statistics.Anime.MinutesWatched, ChaptersRead: profile.Data.Viewer.Statistics.Manga.ChaptersRead, SiteUrl: profile.Data.Viewer.SiteUrl, + AvatarUrl: profile.Data.Viewer.Avatar.Large, } // display profile UI