artwork.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. package core
  2. import (
  3. "errors"
  4. "fmt"
  5. "sort"
  6. "strings"
  7. "sync"
  8. "time"
  9. "codeberg.org/vnpower/pixivfe/v2/session"
  10. "github.com/goccy/go-json"
  11. "net/http"
  12. )
  13. // Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
  14. // Those values are saved in `xRestrict`
  15. // 0: Safe
  16. // 1: R18
  17. // 2: R18G
  18. type xRestrict int
  19. const (
  20. Safe xRestrict = 0
  21. R18 xRestrict = 1
  22. R18G xRestrict = 2
  23. )
  24. var xRestrictModel = map[xRestrict]string{
  25. Safe: "",
  26. R18: "R18",
  27. R18G: "R18G",
  28. }
  29. // Pixiv returns 0, 1, 2 to filter SFW and/or NSFW artworks.
  30. // Those values are saved in `aiType`
  31. // 0: Not rated / Unknown
  32. // 1: Not AI-generated
  33. // 2: AI-generated
  34. type aiType int
  35. const (
  36. Unrated aiType = 0
  37. NotAI aiType = 1
  38. AI aiType = 2
  39. )
  40. var aiTypeModel = map[aiType]string{
  41. Unrated: "Unrated",
  42. NotAI: "Not AI",
  43. AI: "AI",
  44. }
  45. type ImageResponse struct {
  46. Width int `json:"width"`
  47. Height int `json:"height"`
  48. Urls map[string]string `json:"urls"`
  49. }
  50. type Image struct {
  51. Width int
  52. Height int
  53. Small string
  54. Medium string
  55. Large string
  56. Original string
  57. }
  58. type Tag struct {
  59. Name string `json:"tag"`
  60. TranslatedName string `json:"translation"`
  61. }
  62. type Comment struct {
  63. AuthorID string `json:"userId"`
  64. AuthorName string `json:"userName"`
  65. Avatar string `json:"img"`
  66. Context string `json:"comment"`
  67. Stamp string `json:"stampId"`
  68. Date string `json:"commentDate"`
  69. }
  70. type UserBrief struct {
  71. ID string `json:"userId"`
  72. Name string `json:"name"`
  73. Avatar string `json:"imageBig"`
  74. }
  75. type ArtworkBrief struct {
  76. ID string `json:"id"`
  77. Title string `json:"title"`
  78. ArtistID string `json:"userId"`
  79. ArtistName string `json:"userName"`
  80. ArtistAvatar string `json:"profileImageUrl"`
  81. Thumbnail string `json:"url"`
  82. Pages int `json:"pageCount"`
  83. XRestrict int `json:"xRestrict"`
  84. AiType int `json:"aiType"`
  85. Bookmarked any `json:"bookmarkData"`
  86. IllustType int `json:"illustType"`
  87. }
  88. type Illust struct {
  89. ID string `json:"id"`
  90. Title string `json:"title"`
  91. Description HTML `json:"description"`
  92. UserID string `json:"userId"`
  93. UserName string `json:"userName"`
  94. UserAccount string `json:"userAccount"`
  95. Date time.Time `json:"uploadDate"`
  96. Images []Image
  97. Tags []Tag `json:"tags"`
  98. Pages int `json:"pageCount"`
  99. Bookmarks int `json:"bookmarkCount"`
  100. Likes int `json:"likeCount"`
  101. Comments int `json:"commentCount"`
  102. Views int `json:"viewCount"`
  103. CommentDisabled int `json:"commentOff"`
  104. SanityLevel int `json:"sl"`
  105. XRestrict xRestrict `json:"xRestrict"`
  106. AiType aiType `json:"aiType"`
  107. BookmarkData any `json:"bookmarkData"`
  108. Liked bool `json:"likeData"`
  109. User UserBrief
  110. RecentWorks []ArtworkBrief
  111. RelatedWorks []ArtworkBrief
  112. CommentsList []Comment
  113. IsUgoira bool
  114. BookmarkID string
  115. }
  116. func GetUserBasicInformation(r *http.Request, id string) (UserBrief, error) {
  117. var user UserBrief
  118. URL := GetUserInformationURL(id)
  119. response, err := API_GET_UnwrapJson(r.Context(), URL, "")
  120. if err != nil {
  121. return user, err
  122. }
  123. response = session.ProxyImageUrl(r, response)
  124. err = json.Unmarshal([]byte(response), &user)
  125. if err != nil {
  126. return user, err
  127. }
  128. return user, nil
  129. }
  130. func GetArtworkImages(r *http.Request, id string) ([]Image, error) {
  131. var resp []ImageResponse
  132. var images []Image
  133. URL := GetArtworkImagesURL(id)
  134. response, err := API_GET_UnwrapJson(r.Context(), URL, "")
  135. if err != nil {
  136. return nil, err
  137. }
  138. response = session.ProxyImageUrl(r, response)
  139. err = json.Unmarshal([]byte(response), &resp)
  140. if err != nil {
  141. return images, err
  142. }
  143. // Extract and proxy every images
  144. for _, imageRaw := range resp {
  145. var image Image
  146. // this is the original art dimention, not the "regular" art dimension
  147. // the image ratio of "regular" is close to Width/Height
  148. // maybe not useful
  149. image.Width = imageRaw.Width
  150. image.Height = imageRaw.Height
  151. image.Small = imageRaw.Urls["thumb_mini"]
  152. image.Medium = imageRaw.Urls["small"]
  153. image.Large = imageRaw.Urls["regular"]
  154. image.Original = imageRaw.Urls["original"]
  155. images = append(images, image)
  156. }
  157. return images, nil
  158. }
  159. func GetArtworkComments(r *http.Request, id string) ([]Comment, error) {
  160. var body struct {
  161. Comments []Comment `json:"comments"`
  162. }
  163. URL := GetArtworkCommentsURL(id)
  164. response, err := API_GET_UnwrapJson(r.Context(), URL, "")
  165. if err != nil {
  166. return nil, err
  167. }
  168. response = session.ProxyImageUrl(r, response)
  169. err = json.Unmarshal([]byte(response), &body)
  170. if err != nil {
  171. return nil, err
  172. }
  173. return body.Comments, nil
  174. }
  175. func GetRelatedArtworks(r *http.Request, id string) ([]ArtworkBrief, error) {
  176. var body struct {
  177. Illusts []ArtworkBrief `json:"illusts"`
  178. }
  179. // TODO: keep the hard-coded limit?
  180. URL := GetArtworkRelatedURL(id, 96)
  181. response, err := API_GET_UnwrapJson(r.Context(), URL, "")
  182. if err != nil {
  183. return nil, err
  184. }
  185. response = session.ProxyImageUrl(r, response)
  186. err = json.Unmarshal([]byte(response), &body)
  187. if err != nil {
  188. return nil, err
  189. }
  190. return body.Illusts, nil
  191. }
  192. func GetArtworkByID(r *http.Request, id string, full bool) (*Illust, error) {
  193. URL := GetArtworkInformationURL(id)
  194. token := session.GetPixivToken(r)
  195. response, err := API_GET_UnwrapJson(r.Context(), URL, token)
  196. if err != nil {
  197. return nil, err
  198. }
  199. var illust struct {
  200. *Illust
  201. // recent illustrations by same user
  202. Recent map[int]any `json:"userIllusts"`
  203. RawTags json.RawMessage `json:"tags"`
  204. }
  205. // Parse basic illust information
  206. err = json.Unmarshal([]byte(response), &illust)
  207. if err != nil {
  208. return nil, err
  209. }
  210. if illust.BookmarkData != nil {
  211. t := illust.BookmarkData.(map[string]any)
  212. illust.BookmarkID = t["id"].(string)
  213. }
  214. // Begin testing here
  215. wg := sync.WaitGroup{}
  216. cerr := make(chan error, 6)
  217. wg.Add(3)
  218. go func() {
  219. // Get illust images
  220. defer wg.Done()
  221. images, err := GetArtworkImages(r, id)
  222. if err != nil {
  223. cerr <- err
  224. return
  225. }
  226. illust.Images = images
  227. }()
  228. go func() {
  229. // Get basic user information (the URL above does not contain avatars)
  230. defer wg.Done()
  231. var err error
  232. userInfo, err := GetUserBasicInformation(r, illust.UserID)
  233. if err != nil {
  234. cerr <- err
  235. return
  236. }
  237. illust.User = userInfo
  238. }()
  239. go func() {
  240. defer wg.Done()
  241. var err error
  242. // Extract tags
  243. var tags struct {
  244. Tags []struct {
  245. Tag string `json:"tag"`
  246. Translation map[string]string `json:"translation"`
  247. } `json:"tags"`
  248. }
  249. err = json.Unmarshal(illust.RawTags, &tags)
  250. if err != nil {
  251. cerr <- err
  252. return
  253. }
  254. var tagsList []Tag
  255. for _, tag := range tags.Tags {
  256. var newTag Tag
  257. newTag.Name = tag.Tag
  258. newTag.TranslatedName = tag.Translation["en"]
  259. tagsList = append(tagsList, newTag)
  260. }
  261. illust.Tags = tagsList
  262. }()
  263. if full {
  264. wg.Add(3)
  265. go func() {
  266. defer wg.Done()
  267. var err error
  268. // Get recent artworks
  269. ids := make([]int, 0)
  270. for k := range illust.Recent {
  271. ids = append(ids, k)
  272. }
  273. sort.Sort(sort.Reverse(sort.IntSlice(ids)))
  274. idsString := ""
  275. count := min(len(ids), 20)
  276. for i := 0; i < count; i++ {
  277. idsString += fmt.Sprintf("&ids[]=%d", ids[i])
  278. }
  279. recent, err := GetUserArtworks(r, illust.UserID, idsString)
  280. if err != nil {
  281. cerr <- err
  282. return
  283. }
  284. sort.Slice(recent[:], func(i, j int) bool {
  285. left := recent[i].ID
  286. right := recent[j].ID
  287. return numberGreaterThan(left, right)
  288. })
  289. illust.RecentWorks = recent
  290. }()
  291. go func() {
  292. defer wg.Done()
  293. var err error
  294. related, err := GetRelatedArtworks(r, id)
  295. if err != nil {
  296. cerr <- err
  297. return
  298. }
  299. illust.RelatedWorks = related
  300. }()
  301. go func() {
  302. defer wg.Done()
  303. if illust.CommentDisabled == 1 {
  304. return
  305. }
  306. var err error
  307. comments, err := GetArtworkComments(r, id)
  308. if err != nil {
  309. cerr <- err
  310. return
  311. }
  312. illust.CommentsList = comments
  313. }()
  314. }
  315. wg.Wait()
  316. close(cerr)
  317. all_errors := []error{}
  318. for suberr := range cerr {
  319. all_errors = append(all_errors, suberr)
  320. }
  321. err_summary := errors.Join(all_errors...)
  322. if err_summary != nil {
  323. return nil, err_summary
  324. }
  325. // If this artwork is an ugoira
  326. illust.IsUgoira = strings.Contains(illust.Images[0].Original, "ugoira")
  327. return illust.Illust, nil
  328. }