From dc1698e8abe210878280e464cf45ff1c31a36dcb Mon Sep 17 00:00:00 2001 From: VnPower Date: Sat, 20 Jan 2024 11:41:28 +0700 Subject: [PATCH] User page --- core/http/url.go | 30 +++++ core/webapi/user.go | 284 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 1 + pages/user.go | 36 ++++++ 4 files changed, 351 insertions(+) create mode 100644 core/webapi/user.go create mode 100644 pages/user.go diff --git a/core/http/url.go b/core/http/url.go index fd091aa..c163328 100644 --- a/core/http/url.go +++ b/core/http/url.go @@ -28,3 +28,33 @@ func GetRankingCalendarURL(mode string, year, month int) string { return fmt.Sprintf(base, mode, year, month) } + +func GetUserInformationURL(id string) string { + base := "https://www.pixiv.net/ajax/user/%s?full=1" + + return fmt.Sprintf(base, id) +} + +func GetUserArtworksURL(id string) string { + base := "https://www.pixiv.net/ajax/user/%s/profile/all" + + return fmt.Sprintf(base, id) +} + +func GetUserFullArtworkURL(id, ids string) string { + base := "https://www.pixiv.net/ajax/user/%s/profile/illusts?work_category=illustManga&is_first_page=0&lang=en%s" + + return fmt.Sprintf(base, id, ids) +} + +func GetUserBookmarksURL(id, mode string, page int) string { + base := "https://www.pixiv.net/ajax/user/%s/illusts/bookmarks?tag=&offset=%d&limit=48&rest=%s" + + return fmt.Sprintf(base, id, page*48, mode) +} + +func GetFrequentTagsURL(ids string) string { + base := "https://www.pixiv.net/ajax/tags/frequent/illust?%s" + + return fmt.Sprintf(base, ids) +} diff --git a/core/webapi/user.go b/core/webapi/user.go new file mode 100644 index 0000000..516fa79 --- /dev/null +++ b/core/webapi/user.go @@ -0,0 +1,284 @@ +package core + +import ( + "errors" + "fmt" + "html/template" + "math" + "sort" + "strconv" + + session "codeberg.org/vnpower/pixivfe/v2/core/config" + http "codeberg.org/vnpower/pixivfe/v2/core/http" + "github.com/goccy/go-json" + "github.com/gofiber/fiber/v2" +) + +type FrequentTag struct { + Name string `json:"tag"` + TranslatedName string `json:"tag_translation"` +} + +type User struct { + ID string `json:"userId"` + Name string `json:"name"` + Avatar string `json:"imageBig"` + BackgroundImage string `json:"background"` + Following int `json:"following"` + MyPixiv int `json:"mypixivCount"` + Comment template.HTML `json:"commentHtml"` + Webpage string `json:"webpage"` + SocialRaw json.RawMessage `json:"social"` + Artworks []ArtworkBrief `json:"artworks"` + ArtworksCount int + FrequentTags []FrequentTag + Social map[string]map[string]string + Background map[string]interface{} `json:"background"` +} + +func (s *User) ParseSocial() { + if string(s.SocialRaw[:]) == "[]" { + // Fuck Pixiv + return + } + + _ = json.Unmarshal(s.SocialRaw, &s.Social) +} + +func GetFrequentTags(ids string) ([]FrequentTag, error) { + var tags []FrequentTag + + URL := http.GetFrequentTagsURL(ids) + + response, err := http.UnwrapWebAPIRequest(URL, "") + if err != nil { + return nil, err + } + + err = json.Unmarshal([]byte(response), &tags) + if err != nil { + return nil, err + } + + return tags, nil +} + +func GetUserArtworks(c *fiber.Ctx, id, ids string) ([]ArtworkBrief, error) { + var works []ArtworkBrief + + imageProxy := session.GetImageProxy(c) + URL := http.GetUserFullArtworkURL(id, ids) + + resp, err := http.UnwrapWebAPIRequest(URL, "") + if err != nil { + return nil, err + } + resp = ProxyImages(resp, imageProxy) + + var body struct { + Illusts map[int]json.RawMessage `json:"works"` + } + + err = json.Unmarshal([]byte(resp), &body) + if err != nil { + return nil, err + } + + for _, v := range body.Illusts { + var illust ArtworkBrief + err = json.Unmarshal(v, &illust) + + if err != nil { + return nil, err + } + + works = append(works, illust) + } + + return works, nil +} + +func GetUserArtworksID(id, category string, page int) (string, int, error) { + URL := http.GetUserArtworksURL(id) + + resp, err := http.UnwrapWebAPIRequest(URL, "") + if err != nil { + return "", -1, err + } + + var body struct { + Illusts json.RawMessage `json:"illusts"` + Mangas json.RawMessage `json:"manga"` + } + + err = json.Unmarshal([]byte(resp), &body) + if err != nil { + return "", -1, err + } + + var ids []int + var idsString string + + err = json.Unmarshal([]byte(resp), &body) + if err != nil { + return "", -1, err + } + + var illusts map[int]string + var mangas map[int]string + count := 0 + + if err = json.Unmarshal(body.Illusts, &illusts); err != nil { + illusts = make(map[int]string) + } + if err = json.Unmarshal(body.Mangas, &mangas); err != nil { + mangas = make(map[int]string) + } + + // Get the keys, because Pixiv only returns IDs (very evil) + + if category == "illustrations" || category == "artworks" { + for k := range illusts { + ids = append(ids, k) + count++ + } + } + if category == "manga" || category == "artworks" { + for k := range mangas { + ids = append(ids, k) + count++ + } + } + + // Reverse sort the ids + sort.Sort(sort.Reverse(sort.IntSlice(ids))) + + worksNumber := float64(count) + worksPerPage := 30.0 + + if page < 1 || float64(page) > math.Ceil(worksNumber/worksPerPage)+1.0 { + return "", -1, errors.New("Page overflow") + } + + start := (page - 1) * int(worksPerPage) + end := int(min(float64(page)*worksPerPage, worksNumber)) // no overflow + + for _, k := range ids[start:end] { + idsString += fmt.Sprintf("&ids[]=%d", k) + } + + if count == 0 { + // No artworks + return "", -1, errors.New("No artworks found for the current filter.") + } + + return idsString, count, nil +} + +func GetUserArtwork(c *fiber.Ctx, id, category string, page int) (User, error) { + var user User + imageProxy := session.GetImageProxy(c) + token := session.GetToken(c) + + URL := http.GetUserInformationURL(id) + + resp, err := http.UnwrapWebAPIRequest(URL, token) + if err != nil { + return user, err + } + + resp = ProxyImages(resp, imageProxy) + + err = json.Unmarshal([]byte(resp), &user) + if err != nil { + return user, err + } + + if category != "bookmarks" { + ids, count, err := GetUserArtworksID(id, category, page) + if err != nil { + return user, err + } + works, err := GetUserArtworks(c, id, ids) + if err != nil { + return user, err + } + + // IDK but the order got shuffled even though Pixiv sorted the IDs in the response + sort.Slice(works[:], func(i, j int) bool { + left, _ := strconv.Atoi(works[i].ID) + right, _ := strconv.Atoi(works[j].ID) + return left > right + }) + user.Artworks = works + + // Artworks count + user.ArtworksCount = count + + user.FrequentTags, err = GetFrequentTags(ids) + if err != nil { + return user, err + } + } else { + // Bookmarks + works, count, err := GetUserBookmarks(c, id, "show", page) + if err != nil { + return user, err + } + + user.Artworks = works + + // Public bookmarks count + user.ArtworksCount = count + + } + + user.ParseSocial() + + if user.Background != nil { + user.BackgroundImage = user.Background["url"].(string) + } + + return user, nil +} + +func GetUserBookmarks(c *fiber.Ctx, id, mode string, page int) ([]ArtworkBrief, int, error) { + page-- + imageProxy := session.GetImageProxy(c) + URL := http.GetUserBookmarksURL(id, mode, page) + + resp, err := http.UnwrapWebAPIRequest(URL, "") + if err != nil { + return nil, -1, err + } + resp = ProxyImages(resp, imageProxy) + + var body struct { + Artworks []json.RawMessage `json:"works"` + Total int `json:"total"` + } + + err = json.Unmarshal([]byte(resp), &body) + if err != nil { + return nil, -1, err + } + + artworks := make([]ArtworkBrief, len(body.Artworks)) + + for index, value := range body.Artworks { + var artwork ArtworkBrief + + err = json.Unmarshal([]byte(value), &artwork) + if err != nil { + artworks[index] = ArtworkBrief{ + ID: "#", + Title: "Deleted or Private", + Thumbnail: "https://s.pximg.net/common/images/limit_unknown_360.png", + } + continue + } + artworks[index] = artwork + } + + return artworks, body.Total, nil +} diff --git a/main.go b/main.go index e91e571..6dc332f 100644 --- a/main.go +++ b/main.go @@ -111,6 +111,7 @@ func main() { server.Get("discovery", pages.DiscoveryPage) server.Get("ranking", pages.RankingPage) server.Get("rankingCalendar", pages.RankingCalendarPage) + server.Get("users/:id/:category?", pages.UserPage) // Settings group server.Get("login", pages.LoginPage) diff --git a/pages/user.go b/pages/user.go new file mode 100644 index 0000000..55a3615 --- /dev/null +++ b/pages/user.go @@ -0,0 +1,36 @@ +package pages + +import ( + "errors" + "math" + "strconv" + + core "codeberg.org/vnpower/pixivfe/v2/core/webapi" + "github.com/gofiber/fiber/v2" +) + +func UserPage(c *fiber.Ctx) error { + id := c.Params("id") + if _, err := strconv.Atoi(id); err != nil { + return err + } + category := c.Params("category", "artworks") + if !(category == "artworks" || category == "illustrations" || category == "manga" || category == "bookmarks") { + return errors.New("Invalid work category: only illustrations, manga, artworks and bookmarks are available") + } + + page := c.Query("page", "1") + pageInt, _ := strconv.Atoi(page) + + user, err := core.GetUserArtwork(c, id, category, pageInt) + if err != nil { + return err + } + + var worksCount int + + worksCount = user.ArtworksCount + pageLimit := math.Ceil(float64(worksCount) / 30.0) + + return c.Render("pages/user", fiber.Map{"Title": user.Name, "User": user, "Category": category, "PageLimit": int(pageLimit), "Page": pageInt}) +}