mirror of
https://codeberg.org/VnPower/PixivFE
synced 2024-12-06 19:16:23 +01:00
233 lines
6.6 KiB
Go
233 lines
6.6 KiB
Go
package core
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/goccy/go-json"
|
|
"github.com/tidwall/gjson"
|
|
|
|
"codeberg.org/vnpower/pixivfe/v2/i18n"
|
|
"codeberg.org/vnpower/pixivfe/v2/server/session"
|
|
)
|
|
|
|
// Pixivision represents a Pixivision article as returned by the landing page endpoint.
|
|
//
|
|
// Note that this type is less complete than the PixivisionArticle type.
|
|
type Pixivision struct {
|
|
ID string `json:"id"`
|
|
Title string `json:"title"`
|
|
Thumbnail string `json:"thumbnailUrl"`
|
|
URL string `json:"url"`
|
|
}
|
|
|
|
// RecommendedTags groups artworks under a specific tag recommendation.
|
|
type RecommendedTags struct {
|
|
Name string `json:"tag"`
|
|
Artworks []ArtworkBrief
|
|
}
|
|
|
|
// LandingArtworks aggregates various categories of artworks and other related data
|
|
// to be displayed on the landing page.
|
|
type LandingArtworks struct {
|
|
Commissions []ArtworkBrief
|
|
Following []ArtworkBrief
|
|
Recommended []ArtworkBrief
|
|
Newest []ArtworkBrief
|
|
Rankings Ranking
|
|
Users []ArtworkBrief
|
|
Pixivision []Pixivision
|
|
RecommendByTags []RecommendedTags
|
|
}
|
|
|
|
// landingToRankingMode maps landing page modes to their corresponding ranking modes.
|
|
var landingToRankingMode = map[string]string{
|
|
"all": "daily",
|
|
"r18": "daily_r18",
|
|
}
|
|
|
|
// GetLanding retrieves and organizes the landing page data based on the provided mode and user login status.
|
|
//
|
|
// It fetches raw data from the landing URL, parses the JSON response, and populates the LandingArtworks struct.
|
|
func GetLanding(r *http.Request, mode string, isLoggedIn bool) (*LandingArtworks, error) {
|
|
URL := GetLandingURL(mode)
|
|
|
|
resp, err := fetchLandingData(r, URL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
artworks, err := parseArtworks(resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
pages, err := parsePages(resp)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
landing := LandingArtworks{
|
|
Pixivision: pages.Pixivision,
|
|
}
|
|
|
|
// If the user is logged in, populate personalized sections
|
|
if isLoggedIn {
|
|
landing.Following = populateFollowing(pages.Follow, artworks)
|
|
landing.RecommendByTags = populateRecommendedByTags(pages.RecommendedByTags, artworks)
|
|
landing.Recommended = populateRecommended(pages.Recommended.IDs, artworks)
|
|
}
|
|
|
|
landing.Rankings, err = fetchRankings(r, mode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &landing, nil
|
|
}
|
|
|
|
// fetchLandingData retrieves the landing data from the specified URL and validates its JSON format.
|
|
//
|
|
// Returns the raw JSON response as a string if valid.
|
|
func fetchLandingData(r *http.Request, URL string) (string, error) {
|
|
resp, err := API_GET_UnwrapJson(r.Context(), URL, "", r.Header)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
resp = session.ProxyImageUrl(r, resp)
|
|
|
|
if !gjson.Valid(resp) {
|
|
return "", i18n.Errorf("Invalid JSON: %v", resp)
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
// parseArtworks extracts artwork information from the "thumbnails.illust" field
|
|
// of the JSON response and maps them by their ID.
|
|
//
|
|
// This allows for quick lookup of artworks when populating various sections.
|
|
func parseArtworks(resp string) (map[string]ArtworkBrief, error) {
|
|
artworks := make(map[string]ArtworkBrief)
|
|
|
|
stuff := gjson.Get(resp, "thumbnails.illust")
|
|
|
|
// Iterate over each thumbnail entry and unmarshal it into the ArtworkBrief struct
|
|
stuff.ForEach(func(key, value gjson.Result) bool {
|
|
var artwork ArtworkBrief
|
|
if err := json.Unmarshal([]byte(value.String()), &artwork); err != nil {
|
|
// Invalid artwork entries are simply skipped
|
|
return false
|
|
}
|
|
|
|
// Map the artwork by its ID for easy access later
|
|
if artwork.ID != "" {
|
|
artworks[artwork.ID] = artwork
|
|
}
|
|
|
|
return true // Continue iteration
|
|
})
|
|
|
|
return artworks, nil
|
|
}
|
|
|
|
// parsePages unmarshals the "page" field from the JSON response into a structured format.
|
|
func parsePages(resp string) (*struct {
|
|
Pixivision []Pixivision `json:"pixivision"`
|
|
Follow []int `json:"follow"`
|
|
Recommended struct {
|
|
IDs []string `json:"ids"`
|
|
} `json:"recommend"`
|
|
RecommendedByTags []struct {
|
|
Name string `json:"tag"`
|
|
IDs []string `json:"ids"`
|
|
} `json:"recommendByTag"`
|
|
}, error,
|
|
) {
|
|
var pages struct {
|
|
Pixivision []Pixivision `json:"pixivision"`
|
|
Follow []int `json:"follow"`
|
|
Recommended struct {
|
|
IDs []string `json:"ids"`
|
|
} `json:"recommend"`
|
|
// Commented out fields that aren't currently implemented in the frontend
|
|
// EditorRecommended []any `json:"editorRecommend"`
|
|
// UserRecommended []any `json:"recommendUser"`
|
|
// Commission []any `json:"completeRequestIds"`
|
|
RecommendedByTags []struct {
|
|
Name string `json:"tag"`
|
|
IDs []string `json:"ids"`
|
|
} `json:"recommendByTag"`
|
|
}
|
|
|
|
pagesStr := gjson.Get(resp, "page").String()
|
|
|
|
if err := json.Unmarshal([]byte(pagesStr), &pages); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pages, nil
|
|
}
|
|
|
|
// populateArtworks is a generic helper function that maps a slice of string IDs
|
|
// to their corresponding ArtworkBrief objects.
|
|
func populateArtworks(ids []string, artworks map[string]ArtworkBrief) []ArtworkBrief {
|
|
populated := make([]ArtworkBrief, 0, len(ids))
|
|
for _, id := range ids {
|
|
if artwork, exists := artworks[id]; exists {
|
|
populated = append(populated, artwork)
|
|
}
|
|
}
|
|
return populated
|
|
}
|
|
|
|
// populateFollowing converts int IDs to strings and uses the generic helper.
|
|
func populateFollowing(followIDs []int, artworks map[string]ArtworkBrief) []ArtworkBrief {
|
|
stringIDs := make([]string, 0, len(followIDs))
|
|
for _, id := range followIDs {
|
|
stringIDs = append(stringIDs, fmt.Sprint(id))
|
|
}
|
|
return populateArtworks(stringIDs, artworks)
|
|
}
|
|
|
|
// populateRecommended uses the generic helper.
|
|
func populateRecommended(recommendedIDs []string, artworks map[string]ArtworkBrief) []ArtworkBrief {
|
|
return populateArtworks(recommendedIDs, artworks)
|
|
}
|
|
|
|
// populateRecommendedByTags uses the generic helper for each tag's IDs.
|
|
func populateRecommendedByTags(recommends []struct {
|
|
Name string `json:"tag"`
|
|
IDs []string `json:"ids"`
|
|
}, artworks map[string]ArtworkBrief,
|
|
) []RecommendedTags {
|
|
recommendByTags := make([]RecommendedTags, 0, len(recommends))
|
|
|
|
for _, r := range recommends {
|
|
artworksList := populateArtworks(r.IDs, artworks)
|
|
recommendByTags = append(recommendByTags, RecommendedTags{
|
|
Name: r.Name,
|
|
Artworks: artworksList,
|
|
})
|
|
}
|
|
return recommendByTags
|
|
}
|
|
|
|
// fetchRankings retrieves the current rankings based on the selected mode.
|
|
//
|
|
// It maps the landing page mode to the appropriate ranking mode and fetches the ranking data.
|
|
func fetchRankings(r *http.Request, mode string) (Ranking, error) {
|
|
rankingMode, exists := landingToRankingMode[mode]
|
|
if !exists {
|
|
rankingMode = "daily"
|
|
}
|
|
|
|
rankings, err := GetRanking(r, rankingMode, "illust", "", "1")
|
|
if err != nil {
|
|
return Ranking{}, err
|
|
}
|
|
|
|
return rankings, nil
|
|
}
|