def _get_render_data(): return { 'version': get_version(), 'license_email': check_get_license_email(), 'debug': DEBUG, 'debug_sentry': DEBUG_SENTRY, 'debug_posthog': DEBUG_POSTHOG, 'frameless': FRAMELESS, 'frozen': FROZEN, 'is_app': IS_APP, 'platform': PLATFORM, 'session_token': SESSION_TOKEN, 'website_url': WEBSITE_URL, 'device_id': get_device_id(), 'sentry_dsn': '' if DEACTIVATE_SENTRY else get_hidden_value('SENTRY_DSN'), 'posthog_api_key': '' if DEACTIVATE_POSTHOG else get_hidden_value('POSTHOG_API_KEY'), 'disable_error_logging': get_system_setting('disable_error_logging'), 'disable_analytics': get_system_setting('disable_analytics'), }
def get_emails(self, reset=False, batch_size=None): ''' Get slices of emails from our email list, fetching more if needed. ''' # If we don't exist, we have nothing if not self.exists: return [] if reset: self.log('debug', 'Resetting folder') self.seen_email_uids = set() if not batch_size: batch_size = get_system_setting('batch_size') unseen_email_uids = self.email_uids - self.seen_email_uids sorted_unseen_email_uids = sorted(unseen_email_uids, reverse=True) # Select the slice of UIDs email_uids = sorted_unseen_email_uids[:batch_size] # Nothing to fetch? Shortcut! if not email_uids: return [] # Actually fetch the emails emails = self.get_email_headers(email_uids) self.seen_email_uids.update(email_uids) return emails
def get_email_uids(self, use_cache=True): # If we're not a query folder we can try for cached UIDs if use_cache and not self.query: cached_uids = self.cache.get_uids() if cached_uids: self.log( 'debug', f'Loaded {len(cached_uids)} cached message IDs', ) return cached_uids # Searching if isinstance(self.query, (bytes, str)): # Use Gmails X-GM-RAW search extension if available - supports full # Gmail style search queries. if b'X-GM-EXT-1' in self.account.get_capabilities(): search_query = ['X-GM-RAW', self.query] else: # IMAP uses polish notation (operator on the left) search_query = [ 'OR', 'SUBJECT', self.query, 'BODY', self.query ] # Syncing else: sync_days = get_system_setting('sync_days') if sync_days and sync_days > 0: days_ago = date.today() - timedelta(days=sync_days) search_query = ['SINCE', days_ago] else: search_query = ['ALL'] self.log('debug', 'Fetching message IDs') with self.get_connection() as connection: message_uids = connection.search(search_query) self.log('debug', f'Fetched {len(message_uids)} message UIDs') uids = set(message_uids) return uids
def get_email_uids(self, use_cache=True): if use_cache and not self.query: cached_uids = self.get_cached_uids() if cached_uids: return cached_uids # Searching if isinstance(self.query, (bytes, str)): # Use Gmails X-GM-RAW search extension if available - supports full # Gmail style search queries. if b'X-GM-EXT-1' in self.account.get_capabilities(): search_query = ['X-GM-RAW', self.query] else: # IMAP uses polish notation (operator on the left) search_query = ['OR', 'SUBJECT', self.query, 'BODY', self.query] # Syncing else: sync_days = get_system_setting('sync_days') if sync_days and sync_days > 0: days_ago = date.today() - timedelta(days=sync_days) search_query = ['SINCE', days_ago] else: search_query = ['ALL'] self.log('debug', 'Fetching message IDs') with self.get_connection() as connection: try: # Certain IMAP servers (Outlook) don't support UTF-8, even in 2022. message_uids = connection.search(search_query) except UnicodeEncodeError: message_uids = connection.search(search_query, charset='utf-8') self.log('debug', f'Fetched {len(message_uids)} message UIDs') uids = set(message_uids) return uids
def sync_emails(self, expected_uid_count=None, check_unread_uids=None): ''' Get new and deleted emails for this folder. ''' # If we don't exist, try again or we have nothing if not self.exists: if not self.check_exists(): return [], [], [] message_uids = self.get_email_uids(use_cache=False) # Check the folder UIDVALIDITY (busts the cache if needed) uids_valid = self.check_cache_validity() uids_changed = False if uids_valid: # Remove existing from new to get anything new new_message_uids = message_uids - self.email_uids # Remove new from existing to get deleted deleted_message_uids = self.email_uids - message_uids uids_changed = ( len(new_message_uids) > 0 or len(deleted_message_uids) > 0 ) else: uids_changed = True # All old uids invalid, so set all old to deleted deleted_message_uids = self.email_uids batch_size = get_system_setting('batch_size') sorted_message_uids = sorted(message_uids, reverse=True) new_message_uids = sorted_message_uids[:batch_size] self.email_uids = message_uids if uids_changed: self.cache_uids() for uid in deleted_message_uids: self.cache.delete_headers(uid) if expected_uid_count: new_message_uids = fix_missing_uids( expected_uid_count, new_message_uids, ) self.log('debug', ( f'Fetched {len(new_message_uids)} new' f'/{len(deleted_message_uids)} deleted message IDs' )) new_emails = {} if new_message_uids: # Now actually fetch & return those emails new_emails = self.get_email_headers(new_message_uids) self.seen_email_uids.update(new_message_uids) read_uids = [] if check_unread_uids: check_unread_uids = [ # remove any deleted UIDs uid for uid in check_unread_uids if uid in message_uids ] read_uids = self.check_update_unread_emails(check_unread_uids) # Return the enw emails & any deleted uids return new_emails, list(deleted_message_uids), read_uids
class JsonEncoder(JSONEncoder): def default(self, obj) -> Union[str, int]: if isinstance(obj, bytes): try: return obj.decode() except UnicodeDecodeError: return obj.decode('utf-8', 'ignore') return super(JsonEncoder, self).default(obj) if DEBUG and not DEBUG_SENTRY: logger.debug('Not enabling Sentry error logging in debug mode...') elif get_system_setting('disable_error_logging') or DEACTIVATE_SENTRY: logger.debug('Disabling Sentry per user settings') else: sentry_sdk.init( dsn=get_hidden_value('SENTRY_DSN'), release=f'kanmail-app@{get_version()}', integrations=[FlaskIntegration()], # Don't send stack variables, potentially containing email data with_locals=False, ) # Random identifier for this Kanmail install (no PII) sentry_sdk.set_user({'id': get_device_id()}) app = Flask( APP_NAME, static_folder=path.join(CLIENT_ROOT, 'static'),