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.
 
 

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