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.

bot.py 12KB

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