server.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. #!/usr/bin/env python3
  2. import urllib.parse
  3. import math
  4. import re
  5. import html as htmllib
  6. import bottle
  7. import api
  8. import config
  9. # from https://s.pximg.net/www/js/build/spa.illust.43512305451e699294de.js
  10. emojis_raw = [(101,"normal"),(102,"surprise"),(103,"serious"),(104,"heaven"),(105,"happy"),(106,"excited"),(107,"sing"),(108,"cry"),(201,"normal2"),(202,"shame2"),(203,"love2"),(204,"interesting2"),(205,"blush2"),(206,"fire2"),(207,"angry2"),(208,"shine2"),(209,"panic2"),(301,"normal3"),(302,"satisfaction3"),(303,"surprise3"),(304,"smile3"),(305,"shock3"),(306,"gaze3"),(307,"wink3"),(308,"happy3"),(309,"excited3"),(310,"love3"),(401,"normal4"),(402,"surprise4"),(403,"serious4"),(404,"love4"),(405,"shine4"),(406,"sweat4"),(407,"shame4"),(408,"sleep4"),(501,"heart"),(502,"teardrop"),(503,"star")]
  11. emojis = { emoji_name: emoji_id for emoji_id, emoji_name in emojis_raw }
  12. def render_header():
  13. html = '<style>details[open] > summary {display: none;}</style>'
  14. html += '<a href="/"><h1>freexiv</h1></a><form action="/search"><input name="q"><input type="submit" value="search"></form>'
  15. return html
  16. def render_illusts_general(illusts):
  17. html = ''
  18. for illust in illusts:
  19. try:
  20. url = urllib.parse.urlsplit(illust['url'])
  21. html += f"<a href='/en/artworks/{illust['id']}'><img src='/{url.netloc}{url.path}' loading='lazy' ></a>"
  22. except KeyError:
  23. pass
  24. return html
  25. def render_illusts_user(illusts):
  26. html = ''
  27. for illust_id, illust in illusts:
  28. url = illust['url']
  29. url_split = urllib.parse.urlsplit(url)
  30. html += f"<a href='/en/artworks/{illust_id}'><img src='/{url_split.netloc}{url_split.path}'></a>"
  31. return html
  32. def render_paged_illusts(illusts, render_fun=render_illusts_general):
  33. num_of_pages = math.ceil(len(illusts) / api.RECOMMENDS_PAGE_SIZE)
  34. html = render_fun(illusts[:api.RECOMMENDS_PAGE_SIZE])
  35. for page in range(1, num_of_pages):
  36. html += '<details><summary>load more</summary><p>'
  37. html += render_fun(illusts[page * api.RECOMMENDS_PAGE_SIZE: page * api.RECOMMENDS_PAGE_SIZE + api.RECOMMENDS_PAGE_SIZE])
  38. for page in range(num_of_pages):
  39. html += '</p></details>'
  40. return html
  41. def render_user_header(user_id, user_top):
  42. html = render_header()
  43. ogp = user_top['body']['extraData']['meta']['ogp']
  44. illusts = user_top['body']['illusts']
  45. if len(illusts) > 0:
  46. for illust_id, illust in illusts.items():
  47. image_url = illust['profileImageUrl']
  48. image_split = urllib.parse.urlsplit(image_url)
  49. html += f"<img src='/{image_split.netloc}{image_split.path}'>"
  50. break
  51. html += htmllib.escape(ogp['title']) + '<p>' + htmllib.escape(ogp['description']) + '</p>'
  52. html += f'<ul><li><a href="/en/users/{user_id}">Home</a></li><li><a href="/en/users/{user_id}/bookmarks/artworks">Bookmarks</a></li></ul>'
  53. return html
  54. def render_pager(p, max_p):
  55. html = '<div>'
  56. if p > 1:
  57. html += '<a href="?p=1"><<</a> '
  58. html += f'<a href="?p={p - 1}"><</a> '
  59. lowest = max(p - 3, 1)
  60. highest = min(lowest + 6, max_p)
  61. if max_p > 6 and highest - lowest < 6:
  62. lowest = highest - 6
  63. for i in range(highest - lowest + 1):
  64. cur = lowest + i
  65. html += f'<a href="?p={cur}">{cur}</a> '
  66. if p < max_p:
  67. html += f'<a href="?p={p + 1}">></a> '
  68. html += f'<a href="?p={max_p}">>></a>'
  69. html += '</div>'
  70. return html
  71. @bottle.get('/')
  72. def landing():
  73. html = render_header()
  74. landing_page = api.fetch_landing_page().json()
  75. html += render_paged_illusts(landing_page['body']['thumbnails']['illust'])
  76. return html
  77. @bottle.get('/en/artworks/<illust_id:int>')
  78. def artworks(illust_id):
  79. html = render_header()
  80. pages = api.fetch_illust_pages(illust_id).json()
  81. for page in pages['body']:
  82. regular_url = page['urls']['regular']
  83. regular_url_split = urllib.parse.urlsplit(regular_url)
  84. original_url = page['urls']['original']
  85. original_url_split = urllib.parse.urlsplit(original_url)
  86. html += f'<a href="/{original_url_split.netloc}{original_url_split.path}"><img src="/{regular_url_split.netloc}{regular_url_split.path}"></a>'
  87. illust = api.fetch_illust(illust_id).json()['body']
  88. html += '<h1>' + htmllib.escape(illust['illustTitle']) + '</h1>'
  89. html += f"<p>{illust['description']}</p>"
  90. html += f"<a href='/en/users/{illust['userId']}'>" + htmllib.escape(illust['userName']) + '</a>'
  91. html += f"<h2>Comments</h2>"
  92. comments = api.fetch_comments(illust_id).json()
  93. for comment in comments['body']['comments']:
  94. img = comment['img']
  95. img_split = urllib.parse.urlsplit(img)
  96. html += f"<div><a href='/en/users/{comment['userId']}'><img src='/{img_split.netloc}{img_split.path}'>{comment['userName']}</a>: "
  97. if len(comment['comment']) != 0:
  98. def replacer(matchobj):
  99. key = matchobj.group(1)
  100. if key in emojis:
  101. return f'<img src="/s.pximg.net/common/images/emoji/{emojis[key]}.png">'
  102. else:
  103. return key
  104. comment = htmllib.escape(comment['comment'])
  105. comment = re.sub('\(([^)]+)\)', replacer, comment)
  106. html += comment
  107. else:
  108. html += f"<img src='/s.pximg.net/common/images/stamp/generated-stamps/{comment['stampId']}_s.jpg'>"
  109. html += "</div>"
  110. recommends = api.fetch_illust_recommends_init(illust_id, api.MAX_RECOMMENDS_PAGE_SIZE).json()
  111. html += "<h2>Recommended</h2>"
  112. html += render_paged_illusts(recommends['body']['illusts'])
  113. return html
  114. @bottle.get('/en/users/<user_id:int>')
  115. def user(user_id):
  116. user_top = api.fetch_user_top(user_id).json()
  117. user_all = api.fetch_user_all(user_id).json()
  118. html = render_user_header(user_id, user_top)
  119. illusts = user_top['body']['illusts']
  120. if len(illusts) > 0:
  121. if len(illusts.items()) == len(user_all['body']['illusts'].items()):
  122. html += render_paged_illusts(list(illusts.items()), render_illusts_user)
  123. else:
  124. illust_ids = list(user_all['body']['illusts'].keys())
  125. max_num_of_ids_per_page = 100
  126. illusts = {}
  127. for page in range(math.ceil(len(illust_ids) / max_num_of_ids_per_page)):
  128. illusts |= api.fetch_user_illusts(user_id, illust_ids[page * max_num_of_ids_per_page: page * max_num_of_ids_per_page + max_num_of_ids_per_page]).json()
  129. html += render_paged_illusts(list(illusts['body']['works'].items()), render_illusts_user)
  130. return html
  131. @bottle.get('/en/users/<user_id:int>/bookmarks/artworks')
  132. def user_bookmarks(user_id):
  133. p = int(bottle.request.params.get('p', default=1))
  134. items_per_page = 48
  135. user_top = api.fetch_user_top(user_id).json()
  136. bookmarks = api.fetch_user_bookmarks(user_id, (p - 1) * items_per_page, items_per_page).json()
  137. html = render_user_header(user_id, user_top)
  138. max_p = math.ceil(bookmarks['body']['total'] / items_per_page)
  139. html += render_pager(p, max_p)
  140. illusts = bookmarks['body']['works']
  141. html += render_illusts_general(illusts)
  142. html += render_pager(p, max_p)
  143. return html
  144. @bottle.get('/user_banner/<user_id:int>')
  145. def user_banner(user_id):
  146. resp = api.fetch_user_banner(user_id)
  147. bottle.response.set_header('content-type', resp.headers.get('content-type'))
  148. return resp.content
  149. @bottle.get('/search')
  150. def search():
  151. html = render_header()
  152. search_term = bottle.request.query.q
  153. search_term_encoded = urllib.parse.quote(search_term)
  154. search_results = api.fetch_search_results(search_term).json()
  155. illusts = search_results['body']['illustManga']['data']
  156. html += render_paged_illusts(illusts)
  157. return html
  158. bottle.run(host=config.BIND_ADDRESS, server=config.SERVER, port=config.BIND_PORT)