user.go 9.2 KB

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