hurr.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. #!/usr/bin/python
  2. import ConfigParser
  3. import datetime
  4. import gntp.notifier
  5. import os
  6. import paramiko
  7. import piexif
  8. import pyperclip
  9. import random
  10. import re
  11. import string
  12. import sys
  13. import threading
  14. import time
  15. import urllib2
  16. from PIL import Image, ExifTags
  17. OLDFILES = OLDFILES_ALT = OLDFILES_DB = []
  18. SKIPPEM = SKIPPEM_ALT = SKIPPEM_DB = False
  19. AMOUNT = 0
  20. SSH = None
  21. GROWL = None
  22. EXIF_ORIENT_TAG = -1
  23. VERSION = '1.12'
  24. MUHCONF = {
  25. 'growlname': None,
  26. 'icondir': None,
  27. 'sshdir': None,
  28. 'sshkey': 'id_rsa',
  29. 'remotebase': None,
  30. 'urlbase': None,
  31. 'scrotdir': None,
  32. 'scrotpattern': None,
  33. 'server': None,
  34. 'joeser': None,
  35. 'archivebase': '',
  36. 'archivecount': 0,
  37. 'archivewait': 0,
  38. 'archivetime': 0,
  39. 'scrotdir_alt': '',
  40. 'scrotpattern_alt': '',
  41. 'scrotdir_db': '',
  42. 'scrotpattern_db': '',
  43. 'exifgps': None,
  44. 'exifrotate': None,
  45. 'fuckexif': None,
  46. }
  47. def printem(msg):
  48. print msg
  49. sys.stdout.flush()
  50. def getscrots():
  51. global MUHCONF, OLDFILES, SKIPPEM
  52. p = re.compile(MUHCONF['scrotpattern'], re.I)
  53. try:
  54. scrots = [f for f in os.listdir(MUHCONF['scrotdir']) if p.match(f)]
  55. if SKIPPEM:
  56. SKIPPEM = False
  57. OLDFILES = scrots
  58. return []
  59. return scrots
  60. except:
  61. SKIPPEM = True
  62. return []
  63. def getscrots_alt():
  64. global MUHCONF, OLDFILES_ALT, SKIPPEM_ALT
  65. if MUHCONF['scrotdir_alt'] == '' or MUHCONF['scrotpattern_alt'] == '':
  66. return []
  67. p = re.compile(MUHCONF['scrotpattern_alt'], re.I)
  68. try:
  69. scrots = [f for f in os.listdir(MUHCONF['scrotdir_alt']) if p.match(f)]
  70. if SKIPPEM_ALT:
  71. SKIPPEM_ALT = False
  72. OLDFILES_ALT = scrots
  73. return []
  74. return scrots
  75. except:
  76. SKIPPEM_ALT = True
  77. return []
  78. def getscrots_db():
  79. global MUHCONF, OLDFILES_DB, SKIPPEM_DB
  80. if MUHCONF['scrotdir_db'] == '' or MUHCONF['scrotpattern_db'] == '':
  81. return []
  82. p = re.compile(MUHCONF['scrotpattern_db'], re.I)
  83. try:
  84. scrots = [f for f in os.listdir(MUHCONF['scrotdir_db']) if p.match(f)]
  85. if SKIPPEM_DB:
  86. SKIPPEM_DB = False
  87. OLDFILES_DB = scrots
  88. return []
  89. return scrots
  90. except:
  91. SKIPPEM_DB = True
  92. return []
  93. def growlnotif(ID, errorid=None, errormsg=None, desc=None):
  94. global GROWL, MUHCONF
  95. growlnaem = MUHCONF['growlname']
  96. icondir = MUHCONF['icondir']
  97. if ID == 0:
  98. GROWL.notify(
  99. noteType='Uploading',
  100. title=growlnaem,
  101. description='Uploading image',
  102. icon=open('%s/UploadingIcon.png' % icondir, 'rb').read()
  103. )
  104. elif ID == 1:
  105. GROWL.notify(
  106. noteType='Complete',
  107. title=growlnaem,
  108. description='Upload complete',
  109. icon=open('%s/CompleteIcon.png' % icondir, 'rb').read()
  110. )
  111. elif ID == 2:
  112. GROWL.notify(
  113. noteType='Error',
  114. title=growlnaem,
  115. description='An error occured [%d]: %s' % (errorid, errormsg),
  116. icon=open('%s/ErrorIcon.png' % icondir, 'rb').read()
  117. )
  118. elif ID == 3:
  119. GROWL.notify(
  120. noteType='Working',
  121. title=growlnaem,
  122. description=desc,
  123. icon=open('%s/WorkingIcon.png' % icondir, 'rb').read()
  124. )
  125. def upload(local, remote):
  126. global AMOUNT, MUHCONF
  127. strippem_gps = (not MUHCONF['exifgps'])
  128. strippem_all = MUHCONF['fuckexif']
  129. rotatem = MUHCONF['exifrotate']
  130. growlnotif(0)
  131. if strippem_gps or rotatem or strippem_all:
  132. try:
  133. growlnotif(3, desc="Checking for EXIF data")
  134. image_exif = Image.open(local)
  135. exif_raw = piexif.load(image_exif.info['exif'])
  136. gotexif = False
  137. for etype in exif_raw.keys():
  138. if exif_raw[etype] != {} and exif_raw[etype] != None:
  139. gotexif = True
  140. break
  141. if gotexif:
  142. if strippem_gps and not strippem_all:
  143. try:
  144. if exif_raw['GPS'] != {}:
  145. exif_raw.pop('GPS')
  146. printem("Stripped GPS EXIF")
  147. except:
  148. pass
  149. if rotatem or strippem_all:
  150. try:
  151. orientation = exif_raw['0th'][EXIF_ORIENT_TAG]
  152. if orientation in [6, 8, 3, 2, 5, 7, 4]:
  153. printem("Found orientation %d instead of 1, rotating/flipping that shit" % (orientation))
  154. growlnotif(3, desc="Rotating image from EXIF orientation tag lol")
  155. exif_raw['0th'][EXIF_ORIENT_TAG] = 1
  156. if orientation is 6:
  157. image_exif = image_exif.rotate(-90, expand=True)
  158. elif orientation is 8:
  159. image_exif = image_exif.rotate(90, expand=True)
  160. elif orientation is 3:
  161. image_exif = image_exif.rotate(180, expand=True)
  162. elif orientation is 2:
  163. image_exif = image_exif.transpose(Image.FLIP_LEFT_RIGHT)
  164. elif orientation is 5:
  165. image_exif = image_exif.rotate(-90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
  166. elif orientation is 7:
  167. image_exif = image_exif.rotate(90, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
  168. elif orientation is 4:
  169. image_exif = image_exif.rotate(180, expand=True).transpose(Image.FLIP_LEFT_RIGHT)
  170. if exif_raw['thumbnail'] != {} and not strippem_all:
  171. exif_raw.pop('thumbnail')
  172. printem("Stripped thumbnail IFD to prevent previews etc from fucking up")
  173. except:
  174. pass
  175. if strippem_all:
  176. printem("Stripping all EXIF data lol")
  177. exif_raw = {}
  178. image_exif.save(local, exif=piexif.dump(exif_raw))
  179. image_exif.close()
  180. except:
  181. pass
  182. try:
  183. SSH.connect(MUHCONF['server'], username=MUHCONF['joeser'], key_filename="%s/%s" % (MUHCONF['sshdir'], MUHCONF['sshkey']));
  184. SSH.exec_command('find %s -type f -size 0 -exec rm "{}" \;' % MUHCONF['remotebase'])
  185. sftp = SSH.open_sftp()
  186. sftp.put(local, '%s/%s' % (MUHCONF['remotebase'], remote))
  187. sftp.close()
  188. SSH.close()
  189. pyperclip.copy('%s/%s' % (MUHCONF['urlbase'], remote))
  190. AMOUNT += 1
  191. return 0
  192. except Exception as e:
  193. try:
  194. growlnotif(2, e.errno, e.strerror)
  195. except:
  196. growlnotif(2, -1, 'Something went wrong y0');
  197. return 1
  198. def archiver():
  199. global SSH, AMOUNT, MUHCONF
  200. while True:
  201. if AMOUNT >= MUHCONF['archivecount']:
  202. SSH.connect(MUHCONF['server'], username=MUHCONF['joeser'], key_filename="%s/%s" % (MUHCONF['sshdir'], MUHCONF['sshkey']));
  203. date = datetime.datetime.now()
  204. folder = "%d-%02d-%02d--%02d:%02d:%02d" % (date.year, date.month, date.day, date.hour, date.minute, date.second)
  205. printem("Ayyy we archiving fam: %s/%s" % (MUHCONF['archivebase'], folder))
  206. SSH.exec_command('mkdir -p %s/%s' % (MUHCONF['archivebase'], folder))
  207. SSH.exec_command('find %s -type f -mmin +%d -exec mv "{}" "%s/%s" \;' % (MUHCONF['remotebase'], MUHCONF['archivetime'], MUHCONF['archivebase'], folder));
  208. checkamount = 'ls -1 %s | wc -l' % MUHCONF['remotebase']
  209. stdin, stdout, stderr = SSH.exec_command(checkamount)
  210. AMOUNT = int(stdout.read().strip())
  211. SSH.close()
  212. time.sleep(MUHCONF['archivewait'])
  213. def main(path):
  214. global OLDFILES, OLDFILES_ALT, OLDFILES_DB, MUHCONF
  215. if path != None:
  216. remotefile = ''.join([random.choice(string.ascii_letters + string.digits + '-_') for c in range(8)]) + os.path.splitext(path)[1]
  217. if upload(path, remotefile) == 0:
  218. growlnotif(1)
  219. if path.startswith("/tmp/"):
  220. printem("Cleaning up tmpfile: %s" % path)
  221. os.remove(path);
  222. sys.exit(0)
  223. while True:
  224. newfiles_mac = newfiles_alt = newfiles_db = []
  225. checkfiles = getscrots()
  226. newfiles_mac = list(set(checkfiles) - set(OLDFILES))
  227. checkfiles_alt = getscrots_alt()
  228. newfiles_alt = list(set(checkfiles_alt) - set(OLDFILES_ALT))
  229. checkfiles_db = getscrots_db()
  230. newfiles_db = list(set(checkfiles_db) - set(OLDFILES_DB))
  231. if len(newfiles_mac) > 0:
  232. runUpload(newfiles_mac, MUHCONF['scrotdir']);
  233. OLDFILES = checkfiles
  234. if len(newfiles_alt) > 0:
  235. runUpload(newfiles_alt, MUHCONF['scrotdir_alt'])
  236. OLDFILES_ALT = checkfiles_alt
  237. if len(newfiles_db) > 0:
  238. runUpload(newfiles_db, MUHCONF['scrotdir_db']);
  239. OLDFILES_DB = checkfiles_db
  240. time.sleep(3)
  241. def runUpload(newfiles, basepath):
  242. try:
  243. for file in newfiles:
  244. path = '%s/%s' % (basepath, file)
  245. remotefile = ''.join([random.choice(string.ascii_letters + string.digits + '-_') for c in range(8)]) + (os.path.splitext(file)[1])
  246. printem("Uploading %s to %s" % (path, remotefile))
  247. if upload(path, remotefile) == 0:
  248. growlnotif(1)
  249. time.sleep(5)
  250. except:
  251. return False
  252. return True
  253. if __name__ == "__main__":
  254. muhdir = os.path.dirname(os.path.realpath(__file__))
  255. MUHCONF['icondir'] = "%s/icinz" % muhdir
  256. Config = ConfigParser.ConfigParser()
  257. gottem = Config.read(muhdir + "/hurr.conf")
  258. printem("Starting up HurrDroppert v%s lol" % VERSION)
  259. printem("Parsing config: %s/hurr.conf\r\n" % muhdir)
  260. if gottem == []:
  261. printem("Unable to read %s/hurr.conf" % muhdir)
  262. sys.exit(1)
  263. for csect in Config.sections():
  264. errd = False
  265. for copt in Config.options(csect):
  266. try:
  267. if copt in MUHCONF:
  268. cval = None
  269. if copt in ["archivecount", "archivewait", "archivetime"]:
  270. cval = Config.getint(csect, copt)
  271. elif copt in ["exifgps", "exifrotate", "fuckexif"]:
  272. cval = Config.getboolean(csect, copt)
  273. else:
  274. cval = (Config.get(csect, copt)).strip();
  275. if cval in MUHCONF.keys():
  276. cval = MUHCONF[cval]
  277. if cval[0] == '~':
  278. printem("[%s] Expanding special path characters in %s" % (copt, cval))
  279. cval = os.path.expanduser(cval)
  280. if copt.find("scrotpattern") != -1:
  281. try:
  282. p = re.compile(cval, re.I)
  283. except re.error as e:
  284. errd = True
  285. printem("Invalid regular expression for %s: %s" % (copt, cval))
  286. printem("Regex error: [%s]" % e.message)
  287. if copt in MUHCONF.keys() and not cval in [None, -1, '']:
  288. MUHCONF[copt] = cval
  289. except:
  290. if errd == False:
  291. printem("Invalid value '%s' for %s" % (cval, copt))
  292. if errd == True:
  293. sys.exit(1)
  294. if None in MUHCONF.values():
  295. printem("Incomplete config file lol")
  296. sys.exit(1)
  297. for copt in MUHCONF.keys():
  298. cval = MUHCONF[copt]
  299. printem("** %s = %s" % (copt, cval))
  300. for oi in ExifTags.TAGS.keys():
  301. if ExifTags.TAGS[oi] == 'Orientation':
  302. EXIF_ORIENT_TAG = oi
  303. break
  304. printem("\r\nGetting current directory contents")
  305. OLDFILES = getscrots()
  306. OLDFILES_ALT = getscrots_alt()
  307. OLDFILES_DB = getscrots_db()
  308. printem("Setting up SSH using key pair: %s/%s{,.pub}" % (MUHCONF['sshdir'], MUHCONF['sshkey']))
  309. SSH = paramiko.SSHClient()
  310. SSH.known_hosts = None
  311. printem("Loading known_hosts from %s/known_hosts" % MUHCONF['sshdir'])
  312. SSH.load_host_keys(MUHCONF['sshdir'] + "/known_hosts")
  313. SSH.set_missing_host_key_policy(paramiko.AutoAddPolicy())
  314. printem("Setting up Growl")
  315. GROWL = gntp.notifier.GrowlNotifier(
  316. applicationName = MUHCONF['growlname'],
  317. notifications = ['Uploading', 'Complete', 'Error', 'Working'],
  318. defaultNotifications = ['Uploading', 'Complete', 'Error', 'Working']
  319. )
  320. GROWL.register()
  321. printem("Checking for daemon mode")
  322. if len(sys.argv) > 1:
  323. printem("Seems to be a quick file upload, checking existence of %s" % sys.argv[1])
  324. temppath = ' '.join(sys.argv[1:])
  325. if temppath.startswith("https://") or temppath.startswith("http://"):
  326. printem("Seems to be a URL too, checkin em sanity lol")
  327. try:
  328. checkem = urllib2.urlopen(temppath)
  329. muhext = re.sub(r'([?&].*)+', '', temppath)
  330. muhext = os.path.splitext(muhext)[1].strip().lower()
  331. if not muhext:
  332. raise Exception("unable to parse extension lol")
  333. muhext = muhext.split(':')[0]
  334. temppath = "/tmp/%d%s" % (int(time.time()), muhext)
  335. with open(temppath, "wb") as tmpf:
  336. tmpf.write(checkem.read())
  337. except Exception as muherr:
  338. printem("Unable to download from URL fam: %s" % (str(muherr)))
  339. sys.exit(1)
  340. elif os.path.isfile(temppath) == False:
  341. printem("File doesn't exist lol")
  342. sys.exit(1)
  343. printem("Calling main function")
  344. main(temppath)
  345. else:
  346. printem("Setting up threads")
  347. run_event = threading.Event()
  348. run_event.set()
  349. threads = []
  350. args = []
  351. printem("Getting current image count")
  352. SSH.connect(MUHCONF['server'], username=MUHCONF['joeser'], key_filename="%s/%s" % (MUHCONF['sshdir'], MUHCONF['sshkey']));
  353. checkamount = 'ls -1 %s | wc -l' % MUHCONF['remotebase']
  354. stdin, stdout, stderr = SSH.exec_command(checkamount)
  355. AMOUNT = int(stdout.read().strip())
  356. SSH.close()
  357. if MUHCONF['archivebase'] != '' and MUHCONF['archivecount'] > 0 and MUHCONF['archivewait'] > 0 and MUHCONF['archivetime'] > 0:
  358. tarchiver = threading.Thread(target=archiver)
  359. tarchiver.daemon = True
  360. tarchiver.start()
  361. printem("Archiver thread created (folder: %s)" % MUHCONF['archivebase'])
  362. else:
  363. printem("Archiving disabled")
  364. args.append(None)
  365. tmain = threading.Thread(target=main, args=args)
  366. tmain.daemon = True
  367. tmain.start()
  368. threads.append(tmain)
  369. printem("Started up, calling main function")
  370. try:
  371. while 1:
  372. time.sleep(.1)
  373. except KeyboardInterrupt:
  374. printem("Attempting to close threads")
  375. run_event.clear()
  376. for thread in threads:
  377. thread.join(5)
  378. printem("Threads successfully closed")