Un bot qui récupère une image random en local et la publie
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

284 lines
12 KiB

  1. #!/usr/bin/env python3
  2. # coding: utf-8
  3. # -*- coding: utf-8 -*-
  4. # A Fediverse (decentralized social network, for instance using Mastodon) bot
  5. from mastodon import StreamListener
  6. from lxml import html
  7. from logging.handlers import RotatingFileHandler
  8. from pprint import pprint
  9. from random import randint
  10. from utils.config import get_parameter, init_log, init_mastodon
  11. from PIL import Image
  12. from io import BytesIO
  13. import requests, os, random, sys, time, json, logging, argparse, re, shutil
  14. config_file = "config.txt"
  15. secrets_filepath = get_parameter("secrets_filepath", config_file)
  16. log_filepath = get_parameter("log_filepath", config_file)
  17. blacklist_filepath = get_parameter("blacklist_filepath", config_file)
  18. collection_filepath = get_parameter("collection_filepath", config_file)
  19. log = init_log(log_filepath)
  20. mastodon = init_mastodon(config_file, secrets_filepath)
  21. blacklist_file = open(blacklist_filepath,'r')
  22. BLACKLIST = json.loads(blacklist_file.read())
  23. blacklist_file.close()
  24. mime_dict = {'.jpg': 'image/jpeg', '.jpe': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif'}
  25. def post_img_local(mastodon, text, log, config):
  26. img_path = get_parameter("img_path", config)
  27. continu = True;
  28. while continu:
  29. secure_random = random.SystemRandom()
  30. file = secure_random.choice(os.listdir(img_path+"/"))
  31. if os.path.isdir(img_path+file):
  32. img_path = img_path+file+"/"
  33. else:
  34. if ".zip" not in file:
  35. continu = False
  36. im = Image.open(img_path+ file)
  37. width, height = im.size
  38. NEW_WIDTH = 2048
  39. if width > 2048:
  40. difference_percent = NEW_WIDTH / width
  41. new_height = height * difference_percent
  42. size = new_height, NEW_WIDTH
  43. im = im.resize((int(NEW_WIDTH), int(new_height)))
  44. im.save('resize_img.jpg')
  45. file = "resize_img.jpg"
  46. shutil.copyfile(file, "/tmp/"+file)
  47. else:
  48. log.debug("no resize")
  49. shutil.copyfile(img_path+file, "/tmp/"+file)
  50. image_byte = open("/tmp/"+file, "rb").read()
  51. file, ext = os.path.splitext(file)
  52. os.remove("/tmp/"+file+ext)
  53. try:
  54. mime = mime_dict[str.lower(ext)]
  55. except KeyError:
  56. mime = None;
  57. log.error(ext + " is not present on mime_dict, please add this")
  58. pass
  59. media_dict = mastodon.media_post(image_byte, mime)
  60. return media_dict;
  61. def post_unsplash_random_image(mastodon, log, config):
  62. collection_url = get_parameter("collection_url", config)
  63. unsplash_client_id = get_parameter("unsplash_client_id", config)
  64. collecion_file = open(collection_filepath,'r')
  65. collections = json.loads(collecion_file.read())
  66. collecion_file.close()
  67. count_collection = len(collections)-1
  68. if count_collection > -1:
  69. id_collection = randint(0,count_collection)
  70. collection_url="&collections="+str(collections[id_collection])
  71. else:
  72. collection_url=''
  73. response = requests.get("https://api.unsplash.com/photos/random?client_id="+unsplash_client_id+collection_url)
  74. randim_json = json.loads(response.text)
  75. randim_url = "{}&q=85&crop=entropy&cs=tinysrgb&w=2048&fit=max".format(randim_json['urls']['raw'])
  76. img_response = requests.get(randim_url)
  77. pattern = Image.open(BytesIO(img_response.content), "r").convert('RGB')
  78. pattern.save('output.jpg')
  79. media_dict = mastodon.media_post("output.jpg")
  80. toot = "Shot by {} ({})\n{}".format(randim_json['user']['name'], randim_json['user']['links']['html'], randim_json['links']['html'])
  81. return { 'media_dict': media_dict, 'toot': toot };
  82. def post_img_distant(mastodon, text, log, config):
  83. collection_url = get_parameter("collection_url", config)
  84. collecion_file = open(collection_filepath,'r')
  85. collections = json.loads(collecion_file.read())
  86. collecion_file.close()
  87. count_collection = len(collections)-1
  88. id_collection = randint(0,count_collection)
  89. collection_url = collection_url.replace("<collection>", str(collections[id_collection]))
  90. response = requests.get(collection_url)
  91. pattern = Image.open(BytesIO(response.content), "r").convert('RGB')
  92. pattern.save('output.jpg')
  93. media_dict = mastodon.media_post("output.jpg")
  94. return media_dict;
  95. cleanr = re.compile('<.*?>')
  96. def cleanhtml(raw_html):
  97. cleantext = re.sub(cleanr, '', raw_html)
  98. return cleantext
  99. class BotListener(StreamListener):
  100. def __init__(self, args):
  101. self.args = args
  102. # use only notification
  103. def on_notification(self, notification):
  104. # catch only mention in notification
  105. if notification['type'] == 'mention':
  106. log.debug("Got a mention")
  107. if notification["account"]["bot"] == False:
  108. sender = notification['account']['acct'] # Get sender name
  109. if sender in BLACKLIST:
  110. log.info("Service refused to %s" % sender)
  111. return
  112. sender_hour_filename = "limiter/hour/" + sender; # Forge file for limiter
  113. sender_minute_filename = "limiter/minute/" + sender; # Forge file for limiter
  114. if os.path.isfile(sender_hour_filename): # Check if file exist
  115. log.debug("Sender file exist")
  116. statbuf = os.stat(sender_hour_filename)
  117. last_edit = int(statbuf.st_mtime)
  118. ts = int(time.time())
  119. if ts - last_edit > 3599: # check if file is modified 1 hour after last edition
  120. log.debug("file is too old")
  121. f = open(sender_hour_filename,'w')
  122. f.write(str(1)) # reset counter
  123. f.close()
  124. can_continue = True
  125. else:
  126. log.debug("file is young")
  127. f = open(sender_hour_filename,'r+')
  128. limit = int(get_parameter("limit_hour", config_file))
  129. number_of_mention = int(f.read())
  130. if number_of_mention < limit: # limit of mention per hour is limit_hour
  131. log.debug("Sender have less of limit requests")
  132. f.seek(0)
  133. f.write(str(number_of_mention + 1))
  134. can_continue = True
  135. else:
  136. log.debug("Sender have more of limit requests")
  137. can_continue = False # if number of mention is for, user can't receive anything
  138. f.close()
  139. else: # File not exist, create it and initialise it
  140. log.debug("Sender file not exist")
  141. f = open(sender_hour_filename,"w+")
  142. f.write(str(1))
  143. f.close()
  144. can_continue = True
  145. if can_continue:
  146. if os.path.isfile(sender_minute_filename): # Check if file exist
  147. log.debug("Sender file exist")
  148. statbuf = os.stat(sender_minute_filename)
  149. last_edit = int(statbuf.st_mtime)
  150. ts = int(time.time())
  151. if ts - last_edit > 59: # check if file is modified 1 minute after last edition
  152. log.debug("file is too old")
  153. f = open(sender_minute_filename,'w')
  154. f.write(str(1)) # reset counter
  155. f.close()
  156. can_continue = True
  157. else:
  158. log.debug("file is young")
  159. f = open(sender_minute_filename,'r+')
  160. limit = int(get_parameter("limit", config_file))
  161. number_of_mention = int(f.read())
  162. if number_of_mention < limit: # limit of mention per minute is 4
  163. log.debug("Sender have less of limit requests")
  164. f.seek(0)
  165. f.write(str(number_of_mention + 1))
  166. can_continue = True
  167. else:
  168. log.debug("Sender have more of limit requests")
  169. can_continue = False # if number of mention is for, user can't receive anything
  170. file = open(sender_hour_filename,'r+')
  171. number_of_mention = int(file.read())
  172. file.seek(0)
  173. file.write(str(number_of_mention - 1))
  174. file.close()
  175. f.close()
  176. else: # File not exist, create it and initialise it
  177. log.debug("Sender file not exist")
  178. f = open(sender_minute_filename,"w+")
  179. f.write(str(1))
  180. f.close()
  181. can_continue = True
  182. if can_continue:
  183. id = notification['status']['id']
  184. visibility = notification['status']['visibility']
  185. if visibility == 'public':
  186. visibility = 'unlisted'
  187. mentions = notification['status']['mentions']
  188. text = "@" + notification['status']["account"]["acct"] + " "
  189. for mention in mentions:
  190. if mention["acct"] != get_parameter("name_bot", config_file):
  191. text = text + "@" + mention["acct"] + " "
  192. if get_parameter("sensitive", config_file) == "yes":
  193. sensitive = True
  194. else:
  195. sensitive = False
  196. if self.args.source == "local":
  197. media_dict = post_img_local(mastodon, get_parameter("default_text", config_file), log, config_file)
  198. elif self.args.source == "distant":
  199. media_dict = post_img_distant(mastodon, get_parameter("default_text", config_file), log, config_file)
  200. elif self.args.source == "unsplash-random":
  201. resp = post_unsplash_random_image(mastodon, log, config_file)
  202. text = text + "\n" + resp['toot']
  203. media_dict = resp['media_dict']
  204. mastodon.status_post(text, id, media_ids=[media_dict], sensitive=sensitive, visibility=visibility, spoiler_text=get_parameter("spoiler_text", config_file))
  205. else:
  206. log.debug("no picture send :(")
  207. pass
  208. else:
  209. log.debug("Nevermind")
  210. def main():
  211. parser = argparse.ArgumentParser(description='Choose between image or streaming')
  212. parser.add_argument("-i", "--img", action='store_true', help="post image")
  213. parser.add_argument("-s", "--source", help="Source of image [ local | distant | unsplash-random ]")
  214. parser.add_argument("--stream", action="store_true", help="stream user profile")
  215. args = parser.parse_args()
  216. if args.img:
  217. text = get_parameter("default_text", config_file)
  218. if args.source == "local":
  219. media_dict = post_img_local(mastodon, get_parameter("default_text", config_file), log, config_file)
  220. elif args.source == "distant":
  221. media_dict = post_img_distant(mastodon, get_parameter("default_text", config_file), log, config_file)
  222. elif args.source == "unsplash-random":
  223. resp = post_unsplash_random_image(mastodon, log, config_file)
  224. text = resp['toot']
  225. media_dict = resp['media_dict']
  226. if get_parameter("sensitive", config_file) == "yes":
  227. sensitive = True
  228. else:
  229. sensitive = False
  230. mastodon.status_post(text, None, media_ids=[media_dict], sensitive=sensitive, visibility='public', spoiler_text=get_parameter("spoiler_text", config_file))
  231. sys.exit()
  232. elif args.stream:
  233. stream = BotListener(args);
  234. while True:
  235. try:
  236. log.info("Start listening...")
  237. mastodon.stream_user(stream)
  238. except Exception as error:
  239. log.warning('General exception caught: ' + str(error))
  240. time.sleep(0.5)
  241. else:
  242. print("Require an argument. Use --help to display help")
  243. main()