main.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. #!/usr/bin/env python3
  2. # Copyright (c) 2015 Tom Li
  3. #
  4. # Permission is hereby granted, free of charge, to any person obtaining a copy
  5. # of this software and associated documentation files (the "Software"), to deal
  6. # in the Software without restriction, including without limitation the rights
  7. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. # copies of the Software, and to permit persons to whom the Software is
  9. # furnished to do so, subject to the following conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included in
  12. # all copies or substantial portions of the Software.
  13. #
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. # THE SOFTWARE.
  21. import os
  22. import sys
  23. import re
  24. import json
  25. from subprocess import call
  26. from time import sleep
  27. import argparse
  28. import pycurl
  29. import curl
  30. # TODO:
  31. # * tag filters
  32. # * better error handling
  33. def fequal(a, b):
  34. return abs(a - b) < 0.001
  35. def ratio(a, b):
  36. return a / b
  37. def detect_desktop_environment():
  38. desktop_env = "unknown"
  39. if os.environ.get("KDE_FULL_SESSION") == "true":
  40. desktop_env = "kde"
  41. elif os.environ.get("GNOME_DESKTOP_SESSION_ID"):
  42. desktop_env = "gnome"
  43. elif os.environ.get("MATE_DESKTOP_SESSION_ID"):
  44. desktop_env = "mate"
  45. elif sys.platform == "win32":
  46. desktop_env = "windows"
  47. return desktop_env
  48. def set_curl_options(curl):
  49. try:
  50. curl.set_option(pycurl.HTTP_VERSION, pycurl.CURL_HTTP_VERSION_2_0)
  51. except AttributeError:
  52. pass
  53. ssl_library = pycurl.version_info()[5]
  54. if "OpenSSL" in ssl_library or "LibreSSL" in ssl_library:
  55. curl.set_option(pycurl.SSL_CIPHER_LIST, "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384")
  56. elif "GnuTLS".lower() in ssl_library.lower(): # not sure about the capitalization, use lower case
  57. curl.set_option(pycurl.SSL_CIPHER_LIST, "PFS")
  58. else:
  59. print("Warning: unknown/untested SSL/TLS library")
  60. def pick_up_a_url(curl, r18=False):
  61. URL_SAFE = "https://konachan.net/post/random"
  62. URL_R18 = "https://konachan.com/post/random"
  63. # XXX: Side-effect on the curl instance, but it doesn't matter.
  64. curl.set_option(pycurl.FOLLOWLOCATION, False)
  65. if r18:
  66. curl.get(URL_R18)
  67. else:
  68. curl.get(URL_SAFE)
  69. url = curl.get_info(pycurl.REDIRECT_URL)
  70. print("Selected", url)
  71. return url
  72. def fetch_image_info(curl, image_page_url):
  73. html = curl.get(image_page_url).decode("UTF-8")
  74. json_info = re.findall('(?<=Post.register_resp\\().*(?=\\);)', html)[0]
  75. return json.loads(json_info)
  76. def download_image(curl, image_url, filename):
  77. def progress(dwn_total, dwn, up_total, up):
  78. global progress_prev
  79. if dwn_total == 0:
  80. return
  81. progress = int(dwn / dwn_total * 100)
  82. if progress != progress_prev and progress % 10 == 0:
  83. print("downloaded", "%d%%" % progress)
  84. progress_prev = progress
  85. print("\nnow downloading %s..." % filename)
  86. # XXX: Side-effect on the curl instance, but it doesn't matter.
  87. global progress_prev
  88. progress_prev = 0
  89. curl.set_option(pycurl.NOPROGRESS, 0)
  90. curl.set_option(pycurl.PROGRESSFUNCTION, progress)
  91. # HACK: curl doesn't truncate the old data, multiple GETs would not flush the old
  92. # data from the buffer and it results in returning both previous data and new data.
  93. # It is uncertain if this behavior is a feature or a bug due to the fact that curl.Curl()
  94. # is rarely used and poorly documented. After all, it is just a simple class around 100
  95. # lines... Thus we manually truncate the data here.
  96. # See also: https://stackoverflow.com/questions/4330812/how-do-i-clear-a-stringio-object
  97. curl.payload_io.truncate(0)
  98. curl.payload_io.seek(0)
  99. data = curl.get(image_url)
  100. with open(filename, "wb") as file:
  101. file.write(data)
  102. def set_wallpaper(path):
  103. path = os.path.realpath(path)
  104. desktop = detect_desktop_environment()
  105. if desktop == "gnome":
  106. path = "file://%s" % path
  107. call(("gsettings", "set", "org.gnome.desktop.background", "picture-uri", path))
  108. elif desktop == "mate":
  109. call(("gsettings", "set", "org.mate.background", "picture-filename", path))
  110. elif desktop == "kde":
  111. raise NotImplementedError("How to change wallpaper for KDE?")
  112. elif desktop == "windows":
  113. path = '"%s"' % path
  114. call(("reg", "add", "HKCU\Control Panel\Desktop", "/v", "Wallpaper", "/f", "/t", "REG_SZ", "/d", path))
  115. else:
  116. raise NotImplementedError("I don't know how to do it")
  117. def main():
  118. parser = argparse.ArgumentParser(description="Fetch a random wallpaper from Konachan")
  119. parser.add_argument("--width", dest="width", type=int, default=0,
  120. help="download only if the ratio of the image not matches with your suggestion")
  121. parser.add_argument("--height", dest="height", type=int, default=0,
  122. help="download only if the ratio of the image not matches with your suggestion")
  123. parser.add_argument("--r18", dest="r18", action="store_true", default=False,
  124. help="allow to fetch explicit (R-18) images")
  125. args = parser.parse_args()
  126. c = curl.Curl()
  127. set_curl_options(c)
  128. while 1:
  129. random_url = pick_up_a_url(c, args.r18)
  130. full_info = fetch_image_info(c, random_url)
  131. image_info = full_info["posts"][0]
  132. if (args.height and args.width and
  133. not fequal(ratio(image_info["height"], image_info["width"]), ratio(args.height, args.width))):
  134. print("ratio is not perfect, try again...")
  135. sleep(5)
  136. else:
  137. break
  138. print("tags:")
  139. tag_info = full_info["tags"]
  140. for key in tag_info.keys():
  141. print(key)
  142. filename = str(image_info["id"])
  143. if image_info["file_url"].startswith("https"):
  144. file_url = image_info["file_url"]
  145. else:
  146. file_url = "https:%s" % image_info["file_url"]
  147. download_image(c, file_url, filename)
  148. set_wallpaper(filename)
  149. if __name__ == "__main__":
  150. main()