From 757eba1c6cba46b6cfc70349605de6592eeb3698 Mon Sep 17 00:00:00 2001 From: kaylee Date: Fri, 2 Feb 2024 23:16:07 +0000 Subject: [PATCH] finished, but buggy --- uvgotmail.py | 132 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 121 insertions(+), 11 deletions(-) diff --git a/uvgotmail.py b/uvgotmail.py index 1857895..90903d5 100755 --- a/uvgotmail.py +++ b/uvgotmail.py @@ -2,6 +2,7 @@ import argparse import sys import os +import atexit def parseYN(s, default = False): if default == False: @@ -34,7 +35,7 @@ def parseConfig(args=None,config_path=None): if type(config[kv[0]]) == type(True): config[kv[0]] = (kv[1] == 'True') # cast to boolean elif type(config[kv[0]]) == type(1): - config[kv[0]] = parseInt(kv[1]) + config[kv[0]] = int(kv[1]) else: config[kv[0]] = kv[1] except: @@ -88,7 +89,6 @@ def parseConfig(args=None,config_path=None): # make sure at least a server and a username are set if config["server"] == "" or config["user"] == "": print("uvgotmail: failed to start. no imap server or username specified.") - print(config) return config def createConfig(config_path, config,args): @@ -145,12 +145,16 @@ def main(): parser.add_argument("--config", help="specify config file (default: ~/.uvgotmail/config)", default="~/.uvgotmail/config") parser.add_argument("--mutt", help="use existing mutt configuration (experimental)", action="store_true") parser.add_argument("--check", help="run in check mode (put this in PS1)", action="store_true") - parser.add_argument("--no-count", help="don't display how many unread messages there are", action="store_false") + parser.add_argument("--no-count", help="don't display how many unread messages there are", action="store_true") parser.add_argument("--mailbox-symbol", help="specify a string instead of the mailbox emoji",default="") + parser.add_argument("--debug", help="print errors",action="store_true") args = parser.parse_args() - config_settings = parseConfig(args); + global config + config = parseConfig(args); + + config["debug"] = args.debug if(args.daemon == args.check): parser.print_help() @@ -164,17 +168,126 @@ def check(): filename = os.path.expanduser('~/.uvgotmail/unread') if os.path.exists(filename): with open(filename) as f: - unread = int(f.read()); + unread = f.read(); + if unread != '': + unread = int(unread) + else: + unread = 0 if unread == 0: sys.exit(0) pre = "" - if(unread > 1 and countmail == True): + if(unread > 1 and config["no_count"] == False): pre = "("+str(unread)+") " - print(pre+mailchar,end=' ') + print(pre+config["mailbox_symbol"],end=' ') # if there's no unread count file, just exit quietly +def writeUnreadFile(unseen): + try: + with open(os.path.expanduser('~/.uvgotmail/unread'),'w') as f: + f.write(str(len(unseen))) + except Exception as e: + if config["debug"]: print(e) + print("uvgotmail: could not write to unread file") + +# we're implementing this in a disgusting way. i'm sorry. +import time +import socket +import re + +def idleFunction(s,unseen): + s._startIdle = time.time() + if 'IDLE' not in s.capabilities: + raise s.error('Server does not support IDLE') + idle_tag = s._command('IDLE') # start idling + s._get_response() + while line := s._get_line(): + if b'FETCH' in line: + l = str(line) + msg = re.findall(r'\d+',l)[0] + if config["debug"]: print(msg, unseen) + if "seen" in l.lower(): + unseen.remove(msg) + else: + unseen.append(msg) + print(unseen) + writeUnreadFile(unseen) + else: + # we only want to idle for ten minutes maximum. we will use the keep-alive messages to keep track in a single thread. + if(time.time() - s.start > 600): + s.send(b'DONE' + imaplib.CRLF) + return s._command_complete('IDLE', idle_tag) + + +# thanks to trentbuck on github for this awful idea + def daemon(): import imaplib + imaplib.Commands['IDLE'] = ('AUTH', 'SELECTED') + class IMAP4_SSL_plus_IDLE(imaplib.IMAP4_SSL): + def idle(self, unseen): + idleFunction(self,unseen) + class IMAP4_plus_IDLE(imaplib.IMAP4): + def idle(self, unseen): + idleFunction(self,unseen) + + IMAP4_SSL = IMAP4_SSL_plus_IDLE + IMAP4 = IMAP4_plus_IDLE + socket.setdefaulttimeout(120) + + + + # first check if daemon is already running + pidfile = os.path.expanduser('~/.uvgotmail/.run.pid') + if os.path.exists(pidfile): + with open(pidfile) as f: + pid = f.read() + if pid != '': + pid = int(pid) + else: + pid = 0 + running_cmd = os.popen("ps -p" + str(pid) +" -o command").read() + current_cmd = os.popen("ps -p" + str(os.getpid()) +" -o command").read() + if running_cmd == current_cmd or os.path.basename(sys.argv[0]) in running_cmd: + # the process is running; we don't need to start up at all + exit(0) + try: + with open(pidfile,'w') as f: + f.write(str(os.getpid())) + def delPid(pidfile): + os.remove(pidfile) + atexit.register(delPid,pidfile) + except: + print("uvgotmail: failed to write to PID file") + exit(1) + + # main loop + import time + start_time = time.time(); + IMAP4_func = IMAP4_SSL if config["ssl"] else IMAP4 + while True: + try: + with IMAP4_func(config["server"],config["port"]) as conn: + conn.login(user=config["user"],password=config["password"]) + conn.select(mailbox='INBOX',readonly=True) + typ,msgnums = conn.search(None, "UNSEEN"); + unseen = [n.decode() for n in msgnums if n != b''][0].split(' ') + if config["debug"]: print(unseen) + writeUnreadFile(unseen) + #try: + resp = conn.idle(unseen) + #except Exception as e: + if config["debug"]: print(e) + if time.time() - start_time <= 10: + print("uvgotmail: too many imap errors. exiting.") + sys.exit(1) + start_time = time.time(); + + except Exception as e: + if config["debug"]: + print(e) + print("uvgotmail: failed to log in to server. dumping config.") + print(config); + exit(1) # handle ctrl+c gracefully if __name__ == '__main__': @@ -182,8 +295,5 @@ if __name__ == '__main__': main() except KeyboardInterrupt: print('Interrupted') - try: - sys.exit(130) - except SystemExit: - os._exit(130) + exit(130)