user.go 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. package core
  2. import (
  3. "errors"
  4. "fmt"
  5. "html/template"
  6. "math"
  7. "sort"
  8. "codeberg.org/vnpower/pixivfe/v2/session"
  9. "github.com/goccy/go-json"
  10. "github.com/gofiber/fiber/v2"
  11. )
  12. // pixivfe internal data type. not used by pixiv.
  13. type UserArtCategory string
  14. const (
  15. UserArt_Any UserArtCategory = ""
  16. UserArt_AnyAlt UserArtCategory = "artworks"
  17. UserArt_Illustration UserArtCategory = "illustrations"
  18. UserArt_Manga UserArtCategory = "manga"
  19. UserArt_Bookmarks UserArtCategory = "bookmarks" // what this user has bookmarked; not art by this user
  20. UserArt_Novel UserArtCategory = "novels"
  21. )
  22. func (s UserArtCategory) Validate() error {
  23. if s != UserArt_Any &&
  24. s != UserArt_AnyAlt &&
  25. s != UserArt_Illustration &&
  26. s != UserArt_Manga &&
  27. s != UserArt_Bookmarks &&
  28. s != UserArt_Novel {
  29. return fmt.Errorf("Invalid work category: %#v. "+`Only "%s", "%s", "%s", "%s", "%s" and "%s" are available`, s, UserArt_Any, UserArt_AnyAlt, UserArt_Illustration, UserArt_Manga, UserArt_Bookmarks, UserArt_Novel)
  30. } else {
  31. return nil
  32. }
  33. }
  34. type FrequentTag struct {
  35. Name string `json:"tag"`
  36. TranslatedName string `json:"tag_translation"`
  37. }
  38. type User struct {
  39. ID string `json:"userId"`
  40. Name string `json:"name"`
  41. Avatar string `json:"imageBig"`
  42. Following int `json:"following"`
  43. MyPixiv int `json:"mypixivCount"`
  44. Comment template.HTML `json:"commentHtml"`
  45. Webpage string `json:"webpage"`
  46. SocialRaw json.RawMessage `json:"social"`
  47. Artworks []ArtworkBrief `json:"artworks"`
  48. Novels []NovelBrief `json:"novels"`
  49. Background map[string]any `json:"background"`
  50. ArtworksCount int
  51. FrequentTags []FrequentTag
  52. Social map[string]map[string]string
  53. BackgroundImage string
  54. }
  55. func (s *User) ParseSocial() error {
  56. if string(s.SocialRaw[:]) == "[]" {
  57. // Fuck Pixiv
  58. return nil
  59. }
  60. err := json.Unmarshal(s.SocialRaw, &s.Social)
  61. if err != nil {
  62. return err
  63. }
  64. return nil
  65. }
  66. func GetFrequentTags(c *fiber.Ctx, ids string, category UserArtCategory) ([]FrequentTag, error) {
  67. var tags []FrequentTag
  68. var URL string
  69. if category != "novels" {
  70. URL = GetFrequentArtworkTagsURL(ids)
  71. } else {
  72. URL = GetFrequentNovelTagsURL(ids)
  73. }
  74. response, err := UnwrapWebAPIRequest(c.Context(), URL, "")
  75. if err != nil {
  76. return nil, err
  77. }
  78. err = json.Unmarshal([]byte(response), &tags)
  79. if err != nil {
  80. return nil, err
  81. }
  82. return tags, nil
  83. }
  84. func GetUserArtworks(c *fiber.Ctx, id, ids string) ([]ArtworkBrief, error) {
  85. var works []ArtworkBrief
  86. URL := GetUserFullArtworkURL(id, ids)
  87. resp, err := UnwrapWebAPIRequest(c.Context(), URL, "")
  88. if err != nil {
  89. return nil, err
  90. }
  91. resp = session.ProxyImageUrl(c, resp)
  92. var body struct {
  93. Illusts map[int]json.RawMessage `json:"works"`
  94. }
  95. err = json.Unmarshal([]byte(resp), &body)
  96. if err != nil {
  97. return nil, err
  98. }
  99. for _, v := range body.Illusts {
  100. var illust ArtworkBrief
  101. err = json.Unmarshal(v, &illust)
  102. if err != nil {
  103. return nil, err
  104. }
  105. works = append(works, illust)
  106. }
  107. return works, nil
  108. }
  109. func GetUserNovels(c *fiber.Ctx, id, ids string) ([]NovelBrief, error) {
  110. // VnPower: we can merge this function into GetUserArtworks, but I want to make things simple for now
  111. var works []NovelBrief
  112. URL := GetUserFullNovelURL(id, ids)
  113. resp, err := UnwrapWebAPIRequest(c.Context(), URL, "")
  114. if err != nil {
  115. return nil, err
  116. }
  117. resp = session.ProxyImageUrl(c, resp)
  118. var body struct {
  119. Novels map[int]json.RawMessage `json:"works"`
  120. }
  121. err = json.Unmarshal([]byte(resp), &body)
  122. if err != nil {
  123. return nil, err
  124. }
  125. for _, v := range body.Novels {
  126. var novel NovelBrief
  127. err = json.Unmarshal(v, &novel)
  128. if err != nil {
  129. return nil, err
  130. }
  131. works = append(works, novel)
  132. }
  133. return works, nil
  134. }
  135. func GetUserArtworksID(c *fiber.Ctx, id string, category UserArtCategory, page int) (string, int, error) {
  136. URL := GetUserArtworksURL(id)
  137. resp, err := UnwrapWebAPIRequest(c.Context(), URL, "")
  138. if err != nil {
  139. return "", -1, err
  140. }
  141. var body struct {
  142. Illusts json.RawMessage `json:"illusts"`
  143. Mangas json.RawMessage `json:"manga"`
  144. Novels json.RawMessage `json:"novels"`
  145. }
  146. err = json.Unmarshal([]byte(resp), &body)
  147. if err != nil {
  148. return "", -1, err
  149. }
  150. var ids []int
  151. var idsString string
  152. err = json.Unmarshal([]byte(resp), &body)
  153. if err != nil {
  154. return "", -1, err
  155. }
  156. var illusts map[int]string
  157. var mangas map[int]string
  158. var novels map[int]string
  159. count := 0
  160. // Get the keys, because Pixiv only returns IDs (very evil)
  161. if category == UserArt_Illustration || category == UserArt_Any || category == UserArt_AnyAlt {
  162. if err = json.Unmarshal(body.Illusts, &illusts); err != nil {
  163. illusts = make(map[int]string)
  164. }
  165. for k := range illusts {
  166. ids = append(ids, k)
  167. count++
  168. }
  169. }
  170. if category == UserArt_Manga || category == UserArt_Any {
  171. if err = json.Unmarshal(body.Mangas, &mangas); err != nil {
  172. mangas = make(map[int]string)
  173. }
  174. for k := range mangas {
  175. ids = append(ids, k)
  176. count++
  177. }
  178. }
  179. if category == UserArt_Novel {
  180. if err = json.Unmarshal(body.Novels, &novels); err != nil {
  181. novels = make(map[int]string)
  182. }
  183. for k := range novels {
  184. ids = append(ids, k)
  185. count++
  186. }
  187. }
  188. // Reverse sort the ids
  189. sort.Sort(sort.Reverse(sort.IntSlice(ids)))
  190. worksNumber := float64(count)
  191. worksPerPage := 30.0
  192. if page < 1 || float64(page) > math.Ceil(worksNumber/worksPerPage)+1.0 {
  193. return "", -1, errors.New("No page available.")
  194. }
  195. start := (page - 1) * int(worksPerPage)
  196. end := int(min(float64(page)*worksPerPage, worksNumber)) // no overflow
  197. for _, k := range ids[start:end] {
  198. idsString += fmt.Sprintf("&ids[]=%d", k)
  199. }
  200. return idsString, count, nil
  201. }
  202. func GetUserArtwork(c *fiber.Ctx, id string, category UserArtCategory, page int, getTags bool) (User, error) {
  203. var user User
  204. token := session.GetPixivToken(c)
  205. URL := GetUserInformationURL(id)
  206. resp, err := UnwrapWebAPIRequest(c.Context(), URL, token)
  207. if err != nil {
  208. return user, err
  209. }
  210. resp = session.ProxyImageUrl(c, resp)
  211. err = json.Unmarshal([]byte(resp), &user)
  212. if err != nil {
  213. return user, err
  214. }
  215. if category == UserArt_Bookmarks {
  216. // Bookmarks
  217. works, count, err := GetUserBookmarks(c, id, "show", page)
  218. if err != nil {
  219. return user, err
  220. }
  221. user.Artworks = works
  222. // Public bookmarks count
  223. user.ArtworksCount = count
  224. } else if category == UserArt_Novel {
  225. ids, count, err := GetUserArtworksID(c, id, category, page)
  226. if err != nil {
  227. return user, err
  228. }
  229. if count > 0 {
  230. // Check if the user has artworks available or not
  231. works, err := GetUserNovels(c, id, ids)
  232. if err != nil {
  233. return user, err
  234. }
  235. // IDK but the order got shuffled even though Pixiv sorted the IDs in the response
  236. sort.Slice(works[:], func(i, j int) bool {
  237. left := works[i].ID
  238. right := works[j].ID
  239. return numberGreaterThan(left, right)
  240. })
  241. user.Novels = works
  242. if getTags {
  243. user.FrequentTags, err = GetFrequentTags(c, ids, category)
  244. if err != nil {
  245. return user, err
  246. }
  247. }
  248. }
  249. // Artworks count
  250. user.ArtworksCount = count
  251. } else {
  252. ids, count, err := GetUserArtworksID(c, id, category, page)
  253. if err != nil {
  254. return user, err
  255. }
  256. if count > 0 {
  257. // Check if the user has artworks available or not
  258. works, err := GetUserArtworks(c, id, ids)
  259. if err != nil {
  260. return user, err
  261. }
  262. // IDK but the order got shuffled even though Pixiv sorted the IDs in the response
  263. sort.Slice(works[:], func(i, j int) bool {
  264. left := works[i].ID
  265. right := works[j].ID
  266. return numberGreaterThan(left, right)
  267. })
  268. user.Artworks = works
  269. if getTags {
  270. user.FrequentTags, err = GetFrequentTags(c, ids, category)
  271. if err != nil {
  272. return user, err
  273. }
  274. }
  275. }
  276. // Artworks count
  277. user.ArtworksCount = count
  278. }
  279. err = user.ParseSocial()
  280. if err != nil {
  281. return User{}, err
  282. }
  283. if user.Background != nil {
  284. user.BackgroundImage = user.Background["url"].(string)
  285. }
  286. return user, nil
  287. }
  288. func GetUserBookmarks(c *fiber.Ctx, id, mode string, page int) ([]ArtworkBrief, int, error) {
  289. page--
  290. URL := GetUserBookmarksURL(id, mode, page)
  291. resp, err := UnwrapWebAPIRequest(c.Context(), URL, "")
  292. if err != nil {
  293. return nil, -1, err
  294. }
  295. resp = session.ProxyImageUrl(c, resp)
  296. var body struct {
  297. Artworks []json.RawMessage `json:"works"`
  298. Total int `json:"total"`
  299. }
  300. err = json.Unmarshal([]byte(resp), &body)
  301. if err != nil {
  302. return nil, -1, err
  303. }
  304. artworks := make([]ArtworkBrief, len(body.Artworks))
  305. for index, value := range body.Artworks {
  306. var artwork ArtworkBrief
  307. err = json.Unmarshal([]byte(value), &artwork)
  308. if err != nil {
  309. artworks[index] = ArtworkBrief{
  310. ID: "#",
  311. Title: "Deleted or Private",
  312. Thumbnail: "https://s.pximg.net/common/images/limit_unknown_360.png",
  313. }
  314. continue
  315. }
  316. artworks[index] = artwork
  317. }
  318. return artworks, body.Total, nil
  319. }
  320. func numberGreaterThan(l, r string) bool {
  321. if len(l) > len(r) {
  322. return true
  323. }
  324. if len(l) < len(r) {
  325. return false
  326. }
  327. return l > r
  328. }