def attachment(querystr, n): db = Database() query = Query(db, querystr) if query.count_messages() != 1: redirect('/!/%s/' % querystr) else: message = next(iter(query.search_messages())) parts = message.get_message_parts() i = n - 1 if i >= len(parts): redirect('/!/%s/' % querystr) else: part = parts[i] content_type = part.get_content_type() response.content_type = content_type # response.charset = part.get_content_charset() fn = part.get_filename() if fn != None: response.headers['content-disposition'] = 'filename="%s";' % unidecode(fn).replace('"', '') payload = message.get_part(n) if 'html' in content_type.lower(): return clean_html(payload) else: return payload
def part(): db = Database() query_string = '' part_num = 0 first_search_term = 0 for (num, arg) in enumerate(sys.argv[1:]): if arg.startswith('--part='): part_num_str = arg.split("=")[1] try: part_num = int(part_num_str) except ValueError: # just emulating behavior exit(1) elif not arg.startswith('--'): # save the position of the first sys.argv # that is a search term first_search_term = num + 1 if first_search_term: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[first_search_term:]) qry = Query(db, querystr) msgs = [msg for msg in qry.search_messages()] if not msgs: sys.exit(1) elif len(msgs) > 1: raise Exception("search term did not match precisely one message") else: msg = msgs[0] print msg.get_part(part_num)
def thread(thread_id): query = Query(db, 'thread:%s' % thread_id) if not query.count_threads() == 1: return 'Thread not found', 404 messages = [('/messages/' + m.get_message_id(), m.get_header('subject')) \ for m in query.search_messages()] return {'heading': _get_thread(thread_id).get_subject(), 'list': messages}
def part(): db = Database() query_string = '' part_num = 0 first_search_term = 0 for (num, arg) in enumerate(sys.argv[1:]): if arg.startswith('--part='): part_num_str = arg.split("=")[1] try: part_num = int(part_num_str) except ValueError: # just emulating behavior exit(1) elif not arg.startswith('--'): # save the position of the first sys.argv # that is a search term first_search_term = num + 1 if first_search_term: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[first_search_term:]) qry = Query(db,querystr) msgs = [msg for msg in qry.search_messages()] if not msgs: sys.exit(1) elif len(msgs) > 1: raise Exception("search term did not match precisely one message") else: msg = msgs[0] print msg.get_part(part_num)
def get_training_data(progress=False): training_data = [] training_labels = [] db = Database() # query that returns all the messages q = Query(db, '') if progress: count = q.count_messages() n = 0 pbar = ProgressBar(widgets=[Percentage(), Bar(), ETA()], maxval=count).start() data = [] for m in q.search_messages(): if progress: n += 1 pbar.update(n) data.append(m.get_header('To')) data.append(m.get_header('From')) data.append(m.get_header('Subject')) data.append(m.get_part(1).decode("utf8", errors="ignore")) try: training_data.append('\n'.join(data)) except UnicodeDecodeError: print map(lambda x: type(x), data) sys.exit(1) training_labels.append(erase_irrelevant_tags(list(m.get_tags()))) data = [] if progress: pbar.finish() return training_data, training_labels
def message(message_id): query = Query(db, 'id:%s' % message_id) if not query.count_messages() == 1: return 'Message not found', 404 m = next(query.search_messages()) return { 'heading': m.get_header('subject'), 'body': m.get_part(1), }
def notmuch_status(self): with Database() as db: inbox = Query(db, 'not tag:spam and tag:inbox').search_messages() unread = Query(db, 'not tag:spam and tag:unread').search_messages() inbox_count = len(list(inbox)) unread_count = len(list(unread)) return { 'full_text': 'M: {}({})'.format(inbox_count, unread_count), 'cached_until': self.py3.time_in(5) }
def thread(thread_id): query = Query(db, 'thread:%s' % thread_id) if not query.count_threads() == 1: return 'Thread not found', 404 messages = [('/messages/' + m.get_message_id(), m.get_header('subject')) \ for m in query.search_messages()] return { 'heading': _get_thread(thread_id).get_subject(), 'list': messages }
def recent(): query = Query(db, '') messages = [] for i, m in enumerate(query.search_messages()): if i >= 30: break messages.append(('/messages/' + m.get_message_id(), m.get_header('subject'))) return { 'heading': 'Recent messages', 'list': messages, }
def recent(): query = Query(db, '') messages = [] for i, m in enumerate(query.search_messages()): if i >= 30: break messages.append( ('/messages/' + m.get_message_id(), m.get_header('subject'))) return { 'heading': 'Recent messages', 'list': messages, }
def search(): db = Database() query_string = '' sort_order = "newest-first" first_search_term = 0 for (num, arg) in enumerate(sys.argv[1:]): if arg.startswith('--sort='): sort_order = arg.split("=")[1] if not sort_order in ("oldest-first", "newest-first"): raise Exception("unknown sort order") elif not arg.startswith('--'): # save the position of the first sys.argv that is a search term first_search_term = num + 1 if first_search_term: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[first_search_term:]) qry = Query(db, querystr) if sort_order == "oldest-first": qry.set_sort(Query.SORT.OLDEST_FIRST) else: qry.set_sort(Query.SORT.NEWEST_FIRST) threads = qry.search_threads() for thread in threads: print thread
def datesearch(): if request.method == 'POST': tTo = datetime.datetime.strptime(request.form.get('to'), "%d %B, %Y").timetuple() tFrom = datetime.datetime.strptime(request.form.get('from'), "%d %B, %Y").timetuple() #to=int(time.mktime(tTo)),from=int(time.mktime(tFrom)) msgs = list(Query(db,'%i..%i'%(int(time.mktime(tFrom)),int(time.mktime(tTo))) ).search_messages()) return render_template('dateSearch.html',msgs=msgs) #msgs = list(Query(db, request.form.get('search')).search_messages()) return render_template('dateSearch.html')
def get_new_mails(): db = Database() query = Query(db, 'tag:new') data = [] ids = [] m_data = [] for m in query.search_messages(): m_data.append(m.get_header('To')) m_data.append(m.get_header('From')) m_data.append(m.get_header('Subject')) m_data.append(m.get_part(1).decode("utf8", errors="ignore")) try: data.append('\n'.join(m_data)) ids.append(m.get_message_id()) except UnicodeDecodeError: print map(lambda x: type(x), m_data) sys.exit(1) m_data = [] return data, ids
def search(): db = Database() query_string = '' sort_order = "newest-first" first_search_term = 0 for (num, arg) in enumerate(sys.argv[1:]): if arg.startswith('--sort='): sort_order=arg.split("=")[1] if not sort_order in ("oldest-first", "newest-first"): raise Exception("unknown sort order") elif not arg.startswith('--'): # save the position of the first sys.argv that is a search term first_search_term = num + 1 if first_search_term: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[first_search_term:]) qry = Query(db, querystr) if sort_order == "oldest-first": qry.set_sort(Query.SORT.OLDEST_FIRST) else: qry.set_sort(Query.SORT.NEWEST_FIRST) threads = qry.search_threads() for thread in threads: print thread
def update(sessionmaker): ''' Update the notmuch database. In order that updates are fast, this is how state is resumed. 1. Get the date of the most recent email that has been imported. 2. Get the message identifiers of all emails that have been imported. 3. Search for emails that are no more than a week older than the most recent import. This buffer of a week should deal with issues of time zones and unsynchronized clocks. 4. Process emails ascending chronological order. 5. Skip an email if the message ID for the email has already been processed. ''' if offlineimap_is_running(): raise EnvironmentError('In case offlineimap runs "notmuch new", you should stop offlineimap while importing data from notmuch.') session = sessionmaker() most_recent = session.query(func.max(NotmuchMessage.datetime)).scalar() sql_query = session.query(NotmuchMessage.message_id) past_messages = set(row[0] for row in sql_query.distinct()) start_date = (most_recent.date() - datetime.timedelta(weeks = 1)) q = Query(Database(), 'date:%s..' % start_date) q.set_sort(Query.SORT.OLDEST_FIRST) for m in q.search_messages(): message_id = m.get_message_id() if message_id in past_messages: logger.debug('Already imported %s' % message_id) continue past_messages.add(message_id) session.add(message(m)) session.flush() # for foreign key constraints session.add_all(attachments(m)) session.commit() logger.info('Added message "id:%s"' % m.get_message_id())
def search(querystr): db = Database() query = Query(db, querystr) if query.count_messages() == 1: message = next(iter(query.search_messages())) title = message.get_header('subject') try: parts = [(i + 1, part.get_filename('No description')) \ for i, part in enumerate(message.get_message_parts())] body = message.get_part(1) except UnicodeDecodeError: parts = [] body = 'There was an encoding problem with this message.' else: title = 'Results for "%s"' % querystr parts = [] body = None return { 'title': title, 'parts': parts, 'body': body, 'threads': list(hierarchy(query)), }
def show(): entire_thread = False db = Database() out_format = "text" querystr = '' first_search_term = None # ugly homegrown option parsing # TODO: use OptionParser for (num, arg) in enumerate(sys.argv[1:]): if arg == '--entire-thread': entire_thread = True elif arg.startswith("--format="): out_format = arg.split("=")[1] if out_format == 'json': # for compatibility use --entire-thread for json entire_thread = True if not out_format in ("json", "text"): raise Exception("unknown format") elif not arg.startswith('--'): # save the position of the first sys.argv that is a search term first_search_term = num + 1 if first_search_term: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[first_search_term:]) threads = Query(db, querystr).search_threads() first_toplevel = True if out_format == "json": sys.stdout.write("[") for thread in threads: msgs = thread.get_toplevel_messages() if not first_toplevel: if out_format == "json": sys.stdout.write(", ") first_toplevel = False msgs.print_messages(out_format, 0, entire_thread) if out_format == "json": sys.stdout.write("]") sys.stdout.write("\n")
def main(): parser = argparse.ArgumentParser( description="Sync message 'X-Keywords' header with notmuch tags.") parser.add_argument("-V", "--version", action="version", version="%(prog)s " + "v%s (%s)" % (__version__, __date__)) parser.add_argument("-q", "--query", dest="query", required=True, help="notmuch database query string") parser.add_argument("-p", "--db-path", dest="dbpath", help="notmuch database path (default to try user configuration)") parser.add_argument("-n", "--dry-run", dest="dryrun", action="store_true", help="dry run") parser.add_argument("-v", "--verbose", dest="verbose", action="store_true", help="show verbose information") # Exclusive argument group for sync mode exgroup1 = parser.add_mutually_exclusive_group(required=True) exgroup1.add_argument("-m", "--merge-keywords-tags", dest="direction_merge", action="store_true", help="merge 'X-Keywords' and tags and update both") exgroup1.add_argument("-k", "--keywords-to-tags", dest="direction_keywords2tags", action="store_true", help="sync 'X-Keywords' to notmuch tags") exgroup1.add_argument("-t", "--tags-to-keywords", dest="direction_tags2keywords", action="store_true", help="sync notmuch tags to 'X-Keywords'") # Exclusive argument group for tag operation mode exgroup2 = parser.add_mutually_exclusive_group(required=False) exgroup2.add_argument("-a", "--add-only", dest="mode_addonly", action="store_true", help="only add notmuch tags") exgroup2.add_argument("-r", "--remove-only", dest="mode_removeonly", action="store_true", help="only remove notmuch tags") # Parse args = parser.parse_args() # Sync direction if args.direction_merge: sync_direction = SyncDirection.MERGE_KEYWORDS_TAGS elif args.direction_keywords2tags: sync_direction = SyncDirection.KEYWORDS_TO_TAGS elif args.direction_tags2keywords: sync_direction = SyncDirection.TAGS_TO_KEYWORDS else: raise ValueError("Invalid synchronization direction") # Sync mode if args.mode_addonly: sync_mode = SyncMode.ADD_ONLY elif args.mode_removeonly: sync_mode = SyncMode.REMOVE_ONLY else: sync_mode = SyncMode.ADD_REMOVE # if args.dbpath: dbpath = os.path.abspath(os.path.expanduser(args.dbpath)) else: dbpath = None # db = Database(path=dbpath, create=False, mode=Database.MODE.READ_WRITE) dbinfo = get_notmuch_revision(dbpath=dbpath) q = Query(db, args.query) total_msgs = q.count_messages() msgs = q.search_messages() # if args.verbose: print("# Notmuch database path: %s" % dbpath) print("# Database revision: %d (uuid: %s)" % (dbinfo['revision'], dbinfo['uuid'])) print("# Query: %s" % args.query) print("# Sync direction: %s" % sync_direction.name) print("# Sync mode: %s" % sync_mode.name) print("# Total messages to check: %d" % total_msgs) print("# Dryn run: %s" % args.dryrun) # for msg in msgs: kwmsg = KwMessage(msg) kwmsg.sync(direction=sync_direction, mode=sync_mode, dryrun=args.dryrun, verbose=args.verbose) # db.close()
def get_messages(query_string): for msg in Query(db(), query_string).search_messages(): yield MessageProxy(msg)
def index(): db = Database() msgs = Query(db, 'inbox').search_messages() return render_template('index.html',msgs=list(msgs)[:100])
def search(): if request.method == 'POST': msgs = list(Query(db, request.form.get('search')).search_messages()) else: msgs = list(Query(db, request.args.get('q')).search_messages()) return render_template('index.html',msgs=list(msgs)[:100])
def _get_thread(thread_id): querystr = 'thread:' + thread_id thread = next(iter(Query(db, querystr).search_threads())) return thread.get_subject()
def search(querystr): query = Query(db, querystr).search_threads() threads = [('/threads/' + t.get_thread_id(), t.get_subject()) \ for t in query] return {'heading': 'Results for "%s"' % querystr, 'list': threads}
def viewmessage(messageid): msgs = list(Query(db, 'id:%s'%messageid).search_messages()) msgParts = list(msgs[0].get_message_parts()) mail = msgParts[0].as_string() return render_template('index.html',msgs=list(msgs)[:100],view=True,mail=mail)
def main(): # Handle command line options #------------------------------------ # No option given, print USAGE and exit if len(sys.argv) == 1: Notmuch().cmd_usage() #------------------------------------ elif sys.argv[1] == 'setup': """Interactively setup notmuch for first use.""" exit("Not implemented.") #------------------------------------- elif sys.argv[1] == 'new': """Check for new and removed messages.""" Notmuch().cmd_new() #------------------------------------- elif sys.argv[1] == 'help': """Print the help text""" Notmuch().cmd_help(sys.argv[1:]) #------------------------------------- elif sys.argv[1] == 'part': part() #------------------------------------- elif sys.argv[1] == 'search': search() #------------------------------------- elif sys.argv[1] == 'show': show() #------------------------------------- elif sys.argv[1] == 'reply': db = Database() if len(sys.argv) == 2: # no search term. abort exit("Error: notmuch reply requires at least one search term.") # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) msgs = Query(db, querystr).search_messages() print Notmuch().format_reply(msgs) #------------------------------------- elif sys.argv[1] == 'count': if len(sys.argv) == 2: # no further search term, count all querystr = '' else: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) print Database().create_query(querystr).count_messages() #------------------------------------- elif sys.argv[1] == 'tag': # build lists of tags to be added and removed add = [] remove = [] while not sys.argv[2] == '--' and \ (sys.argv[2].startswith('+') or sys.argv[2].startswith('-')): if sys.argv[2].startswith('+'): # append to add list without initial + add.append(sys.argv.pop(2)[1:]) else: # append to remove list without initial - remove.append(sys.argv.pop(2)[1:]) # skip eventual '--' if sys.argv[2] == '--': sys.argv.pop(2) # the rest is search terms querystr = quote_query_line(sys.argv[2:]) db = Database(mode=Database.MODE.READ_WRITE) msgs = Query(db, querystr).search_messages() for msg in msgs: # actually add and remove all tags map(msg.add_tag, add) map(msg.remove_tag, remove) #------------------------------------- elif sys.argv[1] == 'search-tags': if len(sys.argv) == 2: # no further search term print "\n".join(Database().get_all_tags()) else: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) db = Database() msgs = Query(db, querystr).search_messages() print "\n".join([t for t in msgs.collect_tags()]) #------------------------------------- elif sys.argv[1] == 'dump': # TODO: implement "dump <filename>" if len(sys.argv) == 2: f = sys.stdout else: f = open(sys.argv[2], "w") db = Database() query = Query(db, '') query.set_sort(Query.SORT.MESSAGE_ID) msgs = query.search_messages() for msg in msgs: f.write("%s (%s)\n" % (msg.get_message_id(), msg.get_tags())) #------------------------------------- elif sys.argv[1] == 'restore': if len(sys.argv) == 2: print("No filename given. Reading dump from stdin.") f = sys.stdin else: f = open(sys.argv[2], "r") # split the msg id and the tags MSGID_TAGS = re.compile("(\S+)\s\((.*)\)$") db = Database(mode=Database.MODE.READ_WRITE) #read each line of the dump file for line in f: msgs = MSGID_TAGS.match(line) if not msgs: sys.stderr.write("Warning: Ignoring invalid input line: %s" % line) continue # split line in components and fetch message msg_id = msgs.group(1) new_tags = set(msgs.group(2).split()) msg = db.find_message(msg_id) if msg == None: sys.stderr.write( "Warning: Cannot apply tags to missing message: %s\n" % msg_id) continue # do nothing if the old set of tags is the same as the new one old_tags = set(msg.get_tags()) if old_tags == new_tags: continue # set the new tags msg.freeze() # only remove tags if the new ones are not a superset anyway if not (new_tags > old_tags): msg.remove_all_tags() for tag in new_tags: msg.add_tag(tag) msg.thaw() #------------------------------------- else: # unknown command exit("Error: Unknown command '%s' (see \"notmuch help\")" % sys.argv[1])
def count_messages(query_string): return Query(db(), query_string).count_messages()
def main(): # Handle command line options #------------------------------------ # No option given, print USAGE and exit if len(sys.argv) == 1: Notmuch().cmd_usage() #------------------------------------ elif sys.argv[1] == 'setup': """Interactively setup notmuch for first use.""" exit("Not implemented.") #------------------------------------- elif sys.argv[1] == 'new': """Check for new and removed messages.""" Notmuch().cmd_new() #------------------------------------- elif sys.argv[1] == 'help': """Print the help text""" Notmuch().cmd_help(sys.argv[1:]) #------------------------------------- elif sys.argv[1] == 'part': part() #------------------------------------- elif sys.argv[1] == 'search': search() #------------------------------------- elif sys.argv[1] == 'show': show() #------------------------------------- elif sys.argv[1] == 'reply': db = Database() if len(sys.argv) == 2: # no search term. abort exit("Error: notmuch reply requires at least one search term.") # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) msgs = Query(db, querystr).search_messages() print Notmuch().format_reply(msgs) #------------------------------------- elif sys.argv[1] == 'count': if len(sys.argv) == 2: # no further search term, count all querystr = '' else: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) print Database().create_query(querystr).count_messages() #------------------------------------- elif sys.argv[1] == 'tag': # build lists of tags to be added and removed add = [] remove = [] while not sys.argv[2] == '--' and \ (sys.argv[2].startswith('+') or sys.argv[2].startswith('-')): if sys.argv[2].startswith('+'): # append to add list without initial + add.append(sys.argv.pop(2)[1:]) else: # append to remove list without initial - remove.append(sys.argv.pop(2)[1:]) # skip eventual '--' if sys.argv[2] == '--': sys.argv.pop(2) # the rest is search terms querystr = quote_query_line(sys.argv[2:]) db = Database(mode=Database.MODE.READ_WRITE) msgs = Query(db, querystr).search_messages() for msg in msgs: # actually add and remove all tags map(msg.add_tag, add) map(msg.remove_tag, remove) #------------------------------------- elif sys.argv[1] == 'search-tags': if len(sys.argv) == 2: # no further search term print "\n".join(Database().get_all_tags()) else: # mangle arguments wrapping terms with spaces in quotes querystr = quote_query_line(sys.argv[2:]) db = Database() msgs = Query(db, querystr).search_messages() print "\n".join([t for t in msgs.collect_tags()]) #------------------------------------- elif sys.argv[1] == 'dump': if len(sys.argv) == 2: f = sys.stdout else: f = open(sys.argv[2], "w") db = Database() query = Query(db, '') query.set_sort(Query.SORT.MESSAGE_ID) msgs = query.search_messages() for msg in msgs: f.write("%s (%s)\n" % (msg.get_message_id(), msg.get_tags())) #------------------------------------- elif sys.argv[1] == 'restore': if len(sys.argv) == 2: print("No filename given. Reading dump from stdin.") f = sys.stdin else: f = open(sys.argv[2], "r") # split the msg id and the tags MSGID_TAGS = re.compile("(\S+)\s\((.*)\)$") db = Database(mode=Database.MODE.READ_WRITE) #read each line of the dump file for line in f: msgs = MSGID_TAGS.match(line) if not msgs: sys.stderr.write("Warning: Ignoring invalid input line: %s" % line) continue # split line in components and fetch message msg_id = msgs.group(1) new_tags = set(msgs.group(2).split()) msg = db.find_message(msg_id) if msg == None: sys.stderr.write( "Warning: Cannot apply tags to missing message: %s\n" % msg_id) continue # do nothing if the old set of tags is the same as the new one old_tags = set(msg.get_tags()) if old_tags == new_tags: continue # set the new tags msg.freeze() # only remove tags if the new ones are not a superset anyway if not (new_tags > old_tags): msg.remove_all_tags() for tag in new_tags: msg.add_tag(tag) msg.thaw() #------------------------------------- else: # unknown command exit("Error: Unknown command '%s' (see \"notmuch help\")" % sys.argv[1])