epstein-files 1.0.16__py3-none-any.whl → 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
epstein_files/__init__.py CHANGED
@@ -16,13 +16,13 @@ from rich.text import Text
16
16
  from epstein_files.epstein_files import EpsteinFiles, document_cls
17
17
  from epstein_files.documents.document import INFO_PADDING, Document
18
18
  from epstein_files.documents.email import Email
19
- from epstein_files.util.constant.output_files import ALL_EMAILS_PATH, TEXT_MSGS_HTML_PATH, make_clean
19
+ from epstein_files.util.constant.output_files import ALL_EMAILS_PATH, CHRONOLOGICAL_EMAILS_PATH, TEXT_MSGS_HTML_PATH, make_clean
20
20
  from epstein_files.util.env import args
21
21
  from epstein_files.util.file_helper import coerce_file_path, extract_file_id
22
22
  from epstein_files.util.logging import logger
23
23
  from epstein_files.util.output import (print_emails_section, print_json_files, print_json_stats,
24
- print_other_files_section, print_text_messages_section, write_json_metadata, write_urls)
25
- from epstein_files.util.rich import build_highlighter, console, print_header, print_panel, write_html
24
+ print_other_files_section, print_text_messages_section, write_complete_emails_timeline, write_json_metadata, write_urls)
25
+ from epstein_files.util.rich import build_highlighter, console, print_title_page_header, print_title_page_tables, print_panel, write_html
26
26
  from epstein_files.util.timer import Timer
27
27
  from epstein_files.util.word_count import write_word_counts_html
28
28
 
@@ -43,7 +43,10 @@ def generate_html() -> None:
43
43
  print_json_files(epstein_files)
44
44
  exit()
45
45
 
46
- print_header(epstein_files)
46
+ print_title_page_header(epstein_files)
47
+
48
+ if not args.email_timeline:
49
+ print_title_page_tables(epstein_files)
47
50
 
48
51
  if args.colors_only:
49
52
  exit()
@@ -55,6 +58,9 @@ def generate_html() -> None:
55
58
  if args.output_emails:
56
59
  emails_that_were_printed = print_emails_section(epstein_files)
57
60
  timer.print_at_checkpoint(f"Printed {len(emails_that_were_printed):,} emails")
61
+ elif args.email_timeline:
62
+ write_complete_emails_timeline(epstein_files)
63
+ timer.print_at_checkpoint(f"Printed chronological emails table")
58
64
 
59
65
  if args.output_other:
60
66
  if args.uninteresting:
@@ -66,7 +72,14 @@ def generate_html() -> None:
66
72
  timer.print_at_checkpoint(f"Printed {len(files)} other files (skipped {len(epstein_files.other_files) - len(files)})")
67
73
 
68
74
  # Save output
69
- write_html(ALL_EMAILS_PATH if args.all_emails else TEXT_MSGS_HTML_PATH)
75
+ if args.all_emails:
76
+ output_path = ALL_EMAILS_PATH
77
+ elif args.email_timeline:
78
+ output_path = CHRONOLOGICAL_EMAILS_PATH
79
+ else:
80
+ output_path = TEXT_MSGS_HTML_PATH
81
+
82
+ write_html(output_path)
70
83
  logger.warning(f"Total time: {timer.seconds_since_start_str()}")
71
84
 
72
85
  # JSON stats (mostly used for building pytest checks)
@@ -244,6 +244,10 @@ class Document:
244
244
 
245
245
  return (self.timestamp or FALLBACK_TIMESTAMP, sort_id, dupe_idx)
246
246
 
247
+ def source_file_id(self) -> str:
248
+ """Strip off the _1, _2, etc. suffixes for extracted documents."""
249
+ return self.file_id[0:6]
250
+
247
251
  def summary(self) -> Text:
248
252
  """Summary of this file for logging. Brackets are left open for subclasses to add stuff."""
249
253
  txt = Text('').append(self._class_name(), style=self._class_style())
@@ -366,7 +366,7 @@ class Email(Communication):
366
366
  def info_txt(self) -> Text:
367
367
  email_type = 'fwded article' if self.is_fwded_article() else 'email'
368
368
  txt = Text(f"OCR text of {email_type} from ", style='grey46').append(self.author_txt).append(' to ')
369
- return txt.append(self._recipients_txt()).append(highlighter(f" probably sent at {self.timestamp}"))
369
+ return txt.append(self.recipients_txt()).append(highlighter(f" probably sent at {self.timestamp}"))
370
370
 
371
371
  def is_fwded_article(self) -> bool:
372
372
  return bool(self.config and self.config.is_fwded_article)
@@ -382,6 +382,16 @@ class Email(Communication):
382
382
  metadata.update({k: v for k, v in local_metadata.items() if v and k in METADATA_FIELDS})
383
383
  return metadata
384
384
 
385
+ def recipients_txt(self, max_full_names: int = 2) -> Text:
386
+ """Text object with comma separated colored versions of all recipients."""
387
+ recipients = [r or UNKNOWN for r in self.recipients] if len(self.recipients) > 0 else [UNKNOWN]
388
+
389
+ # Use just the last name for each recipient if there's 3 or more recipients
390
+ return join_texts([
391
+ Text(r if len(recipients) <= max_full_names else extract_last_name(r), style=get_style_for_name(r))
392
+ for r in recipients
393
+ ], join=', ')
394
+
385
395
  def subject(self) -> str:
386
396
  return self.header.subject or ''
387
397
 
@@ -390,7 +400,7 @@ class Email(Communication):
390
400
  txt = self._summary()
391
401
 
392
402
  if len(self.recipients) > 0:
393
- txt.append(', ').append(key_value_txt('recipients', self._recipients_txt()))
403
+ txt.append(', ').append(key_value_txt('recipients', self.recipients_txt()))
394
404
 
395
405
  return txt.append(CLOSE_PROPERTIES_CHAR)
396
406
 
@@ -553,16 +563,6 @@ class Email(Communication):
553
563
 
554
564
  return collapse_newlines(text).strip()
555
565
 
556
- def _recipients_txt(self) -> Text:
557
- """Text object with comma separated colored versions of all recipients."""
558
- recipients = [r or UNKNOWN for r in self.recipients] if len(self.recipients) > 0 else [UNKNOWN]
559
-
560
- # Use just the last name for each recipient if there's 3 or more recipients
561
- return join_texts([
562
- Text(r if len(recipients) < 3 else extract_last_name(r), style=get_style_for_name(r))
563
- for r in recipients
564
- ], join=', ')
565
-
566
566
  def _remove_line(self, idx: int) -> None:
567
567
  """Remove a line from self.lines."""
568
568
  num_lines = idx * 2
@@ -29,7 +29,7 @@ from epstein_files.util.env import DOCS_DIR, args, logger
29
29
  from epstein_files.util.file_helper import file_size_str
30
30
  from epstein_files.util.highlighted_group import HIGHLIGHTED_NAMES, HighlightedNames, get_info_for_name, get_style_for_name
31
31
  from epstein_files.util.rich import (DEFAULT_NAME_STYLE, LAST_TIMESTAMP_STYLE, NA_TXT, add_cols_to_table,
32
- print_other_page_link, build_table, console, highlighter, link_text_obj, link_markup, print_author_header, print_centered,
32
+ print_other_page_link, build_table, console, highlighter, link_text_obj, link_markup, print_author_panel, print_centered,
33
33
  print_panel, print_section_header, vertically_pad)
34
34
  from epstein_files.util.search_result import SearchResult
35
35
  from epstein_files.util.timer import Timer
@@ -256,7 +256,7 @@ class EpsteinFiles:
256
256
  unique_emails = [email for email in emails if not email.is_duplicate()]
257
257
  author = _author or UNKNOWN
258
258
 
259
- print_author_header(
259
+ print_author_panel(
260
260
  f"Found {len(unique_emails)} {author} emails starting {emails[0].timestamp.date()} over {conversation_length:,} days",
261
261
  get_style_for_name(author),
262
262
  get_info_for_name(author)
@@ -5,6 +5,14 @@ from epstein_files.util.env import args
5
5
 
6
6
  PAGE_TITLE = ' ∞ Michel de Cryptadamus ∞ '
7
7
 
8
+ if args.all_emails:
9
+ page_type = 'Emails'
10
+ elif args.email_timeline:
11
+ page_type = 'Chronological Emails'
12
+ else:
13
+ page_type = 'Text Messages'
14
+
15
+
8
16
  CONSOLE_HTML_FORMAT = """<!DOCTYPE html>
9
17
  <html>
10
18
  <head>
@@ -18,7 +26,7 @@ CONSOLE_HTML_FORMAT = """<!DOCTYPE html>
18
26
  background-color: {background};
19
27
  }}
20
28
  </style>
21
- """ + f"<title>Epstein {'Emails' if args.all_emails else 'Text Messages'}</title>" + """
29
+ """ + f"<title>Epstein {page_type}</title>" + """
22
30
  </head>
23
31
  <body>
24
32
  <pre style="font-family: Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace; white-space: pre-wrap; overflow-wrap: break-word;">
@@ -239,7 +239,7 @@ OTHER_NAMES = NAMES_TO_NOT_HIGHLIGHT + """
239
239
  gardner gary geoff geoffrey gilbert gloria goldberg gonzalez gould graham greene guarino gwyneth
240
240
  hancock harold harrison harry hay helen hill hirsch hofstadter horowitz hussein
241
241
  ian isaac isaacson
242
- james jamie jane janet jason jen jim joe johnson jones josh julie justin
242
+ james jamie jane janet jason jeffrey jen jim joe johnson jones josh julie justin
243
243
  karl kate kathy kelly kim kruger kyle
244
244
  laurie lawrence leo leonard lenny leslie lieberman louis lynch lynn
245
245
  marcus marianne matt matthew melissa michele michelle moore moscowitz
@@ -6,6 +6,7 @@ from epstein_files.util.constant.strings import EMAIL, TEXT_MESSAGE, SiteType
6
6
  HTML_DIR = Path('docs')
7
7
  EPSTEIN_FILES_NOV_2025 = 'epstein_files_nov_2025'
8
8
  ALL_EMAILS_PATH = HTML_DIR.joinpath(f'all_emails_{EPSTEIN_FILES_NOV_2025}.html')
9
+ CHRONOLOGICAL_EMAILS_PATH = HTML_DIR.joinpath(f'chronological_emails_{EPSTEIN_FILES_NOV_2025}.html')
9
10
  JSON_FILES_JSON_PATH = HTML_DIR.joinpath(f'json_files_from_{EPSTEIN_FILES_NOV_2025}.json')
10
11
  JSON_METADATA_PATH = HTML_DIR.joinpath(f'file_metadata_{EPSTEIN_FILES_NOV_2025}.json')
11
12
  TEXT_MSGS_HTML_PATH = HTML_DIR.joinpath('index.html')
@@ -18,6 +19,7 @@ URLS_ENV = '.urls.env'
18
19
  GH_PAGES_BASE_URL = 'https://michelcrypt4d4mus.github.io'
19
20
  TEXT_MSGS_URL = f"{GH_PAGES_BASE_URL}/epstein_text_messages"
20
21
  ALL_EMAILS_URL = f"{TEXT_MSGS_URL}/{ALL_EMAILS_PATH.name}"
22
+ CHRONOLOGICAL_EMAILS_URL = f"{TEXT_MSGS_URL}/{CHRONOLOGICAL_EMAILS_PATH.name}"
21
23
  JSON_FILES_URL = f"{TEXT_MSGS_URL}/{JSON_FILES_JSON_PATH.name}"
22
24
  JSON_METADATA_URL = f"{TEXT_MSGS_URL}/{JSON_METADATA_PATH.name}"
23
25
  WORD_COUNT_URL = f"{TEXT_MSGS_URL}/{WORD_COUNT_HTML_PATH.name}"
@@ -29,6 +31,7 @@ SITE_URLS: dict[SiteType, str] = {
29
31
 
30
32
  BUILD_ARTIFACTS = [
31
33
  ALL_EMAILS_PATH,
34
+ CHRONOLOGICAL_EMAILS_PATH,
32
35
  # EPSTEIN_WORD_COUNT_HTML_PATH,
33
36
  JSON_FILES_JSON_PATH,
34
37
  JSON_METADATA_PATH,
epstein_files/util/env.py CHANGED
@@ -22,6 +22,7 @@ output = parser.add_argument_group('OUTPUT', 'Options used by epstein_generate.'
22
22
  output.add_argument('--all-emails', '-ae', action='store_true', help='all the emails instead of just the interesting ones')
23
23
  output.add_argument('--all-other-files', '-ao', action='store_true', help='all the non-email, non-text msg files instead of just the interesting ones')
24
24
  output.add_argument('--build', '-b', action='store_true', help='write HTML output to a file')
25
+ output.add_argument('--email-timeline', action='store_true', help='print a table of all emails in chronological order')
25
26
  output.add_argument('--json-files', action='store_true', help='pretty print all the raw JSON data files in the collection and exit')
26
27
  output.add_argument('--json-metadata', action='store_true', help='dump JSON metadata for all files and exit')
27
28
  output.add_argument('--output-emails', '-oe', action='store_true', help='generate emails section')
@@ -70,7 +71,7 @@ args.output_other = args.output_other or args.all_other_files or args.uninterest
70
71
  args.overwrite_pickle = args.overwrite_pickle or (is_env_var_set('OVERWRITE_PICKLE') and not is_env_var_set('PICKLED'))
71
72
  args.width = args.width if is_html_script else None
72
73
  is_any_output_selected = any([arg.startswith('output_') and value for arg, value in vars(args).items()])
73
- is_any_output_selected = is_any_output_selected or args.json_metadata or args.colors_only
74
+ is_any_output_selected = is_any_output_selected or args.colors_only or args.email_timeline or args.json_metadata
74
75
 
75
76
  # Log level args
76
77
  if args.deep_debug:
@@ -143,9 +143,10 @@ HIGHLIGHTED_NAMES = [
143
143
  HighlightedNames(
144
144
  label='Africa',
145
145
  style='light_pink4',
146
- pattern=r'Econet(\s*Wireless)|Ghana(ian)?|Johannesburg|Kenya|Nigerian?|Senegal(ese)?|Serengeti|(South\s*)?African?|(Strive\s*)?Masiyiwa|Tanzania|Ugandan?|Zimbabwe(an)?',
146
+ pattern=r'Buhari|Econet(\s*Wireless)|Ghana(ian)?|Glencore|Goodluck Jonathan|Johannesburg|Kenya|Nigerian?|Okey Enelamah|Senegal(ese)?|Serengeti|(South\s*)?African?|(Strive\s*)?Masiyiwa|Tanzania|Ugandan?|Zimbabwe(an)?',
147
147
  emailers={
148
148
  'Abdoulaye Wade': 'former president of Senegal, father of Karim Wade',
149
+ 'Ivan Glasenberg': "South African former CEO of Glencore, one of the world's largest commodity trading and mining companies",
149
150
  'Karim Wade': 'son of the president of Senegal, facing arrest for corruption, email handle is "Afri zp"',
150
151
  'Miles Alexander': 'Operations Manager Michaelhouse Balgowan KwaZulu-Natal South Africa', # TODO: what's up with this person?
151
152
  'Macky Sall': 'prime minister of Senegal, defeated Abdoulaye Wade',
@@ -154,8 +155,9 @@ HIGHLIGHTED_NAMES = [
154
155
  HighlightedNames(
155
156
  label='bitcoin',
156
157
  style='orange1 bold',
157
- pattern=r'Balaji|bitcoin|block ?chain(\s*capital)?|Brock(\s*Pierce)?|coins?|cr[iy]?pto(currenc(y|ies))?|e-currency|(Gavin )?Andressen|(Howard\s+)?Lutnic?k|(jeffrey\s+)?wernick|Libra|Madars|(Patrick\s*)?Murck|(Ross\s*)?Ulbricht|Silk\s*Road|SpanCash|Tether|virtual\s*currenc(ies|y)|(zero\s+knowledge\s+|zk)pro(of|tocols?)',
158
+ pattern=r'Balaji|bitcoin|block ?chain(\s*capital)?|Brock(\s*Pierce)?|coins?|cr[iy]?pto(currenc(y|ies))?|e-currency|(Gavin )?Andressen|(Howard\s+)?Lutnic?k|Libra|Madars|(Patrick\s*)?Murck|(Ross\s*)?Ulbricht|Silk\s*Road|SpanCash|Tether|virtual\s*currenc(ies|y)|(zero\s+knowledge\s+|zk)pro(of|tocols?)',
158
159
  emailers = {
160
+ 'Jeffrey Wernick': 'former COO of Parler, involved in numerous crypto companies like Bitforex',
159
161
  JEREMY_RUBIN: 'developer/researcher',
160
162
  ANTHONY_SCARAMUCCI: 'Skybridge Capital, FTX investor',
161
163
  },
@@ -354,7 +356,7 @@ HIGHLIGHTED_NAMES = [
354
356
  HighlightedNames(
355
357
  label='India',
356
358
  style='bright_green',
357
- pattern=r'Abraaj|Anna\s*Hazare|(Arif\s*)?Naqvi|(Arvind\s*)?Kejriwal|Hardeep( Pur[ei]e)?|Indian?|InsightsPod|Modi|Mumbai|Tranchulas',
359
+ pattern=r'Abraaj|Anna\s*Hazare|(Arif\s*)?Naqvi|(Arvind\s*)?Kejriwal|Bangalore|Hardeep( Pur[ei]e)?|Indian?|InsightsPod|Modi|Mumbai|(New\s*)?Delhi|Tranchulas',
358
360
  emailers = {
359
361
  ANIL_AMBANI: 'chairman of Reliance Group',
360
362
  VINIT_SAHNI: None,
@@ -423,7 +425,7 @@ HIGHLIGHTED_NAMES = [
423
425
  label='mideast',
424
426
  style='dark_sea_green4',
425
427
  # something like this won't match ever because of word boundary: [-\s]9/11[\s.]
426
- pattern=r"Abdulmalik Al-Makhlafi|Abdullah|Abu\s+Dhabi|Afghanistan|Al[-\s]?Qa[ei]da|Ahmadinejad|Arab|Aramco|Assad|Bahrain|Basiji?|Benghazi|Cairo|Chagoury|Dj[iu]bo?uti|Doha|Dubai|Egypt(ian)?|Emir(at(es?|i))?|Erdogan|Fashi|Gaddafi|(Hamid\s*)?Karzai|Hamad\s*bin\s*Jassim|HBJ|Houthi|Imran\s+Khan|Iran(ian)?|Isi[ls]|Islam(abad|ic|ist)?|Istanbul|Kh?ashoggi|(Kairat\s*)?Kelimbetov|kasshohgi|Kaz(akh|ich)stan|Kazakh?|Kh[ao]menei|Khalid\s*Sheikh\s*Mohammed|KSA|Leban(ese|on)|Libyan?|Mahmoud|Marra[hk]e[cs]h|MB(N|S|Z)|Mid(dle)?\s*East|Mohammed\s+bin\s+Salman|Morocco|Mubarak|Muslim|Nayaf|Pakistani?|Omar|(Osama\s*)?Bin\s*Laden|Osama(?! al)|Palestin(e|ian)|Persian?|Riya(dh|nd)|Saddam|Salman|Saudi(\s+Arabian?)?|Shariah?|SHC|sheikh|shia|(Sultan\s*)?Yacoub|Syrian?|(Tarek\s*)?El\s*Sayed|Tehran|Tunisian?|Turk(ey|ish)|UAE|((Iraq|Iran|Kuwait|Qatar|Yemen)i?)",
428
+ pattern=r"Abdulmalik Al-Makhlafi|Abdullah|Abu\s+Dhabi|Afghanistan|Al[-\s]?Qa[ei]da|Ahmadinejad|Arab|Aramco|Assad|Bahrain|Basiji?|Benghazi|Cairo|Chagoury|Dj[iu]bo?uti|Doha|[DB]ubai|Egypt(ian)?|Emir(at(es?|i))?|Erdogan|Fashi|Gaddafi|(Hamid\s*)?Karzai|Hamad\s*bin\s*Jassim|HBJ|Houthi|Imran\s+Khan|Iran(ian)?|Isi[ls]|Islam(abad|ic|ist)?|Istanbul|Kh?ashoggi|(Kairat\s*)?Kelimbetov|kasshohgi|Kaz(akh|ich)stan|Kazakh?|Kh[ao]menei|Khalid\s*Sheikh\s*Mohammed|KSA|Leban(ese|on)|Libyan?|Mahmoud|Marra[hk]e[cs]h|MB(N|S|Z)|Mid(dle)?\s*East|Mohammed\s+bin\s+Salman|Morocco|Mubarak|Muslim|Nayaf|Pakistani?|Omar|(Osama\s*)?Bin\s*Laden|Osama(?! al)|Palestin(e|ian)|Persian?|Riya(dh|nd)|Saddam|Salman|Saudi(\s+Arabian?)?|Shariah?|SHC|sheikh|shia|(Sultan\s*)?Yacoub|Syrian?|(Tarek\s*)?El\s*Sayed|Tehran|Tunisian?|Turk(ey|ish)|UAE|((Iraq|Iran|Kuwait|Qatar|Yemen)i?)",
427
429
  emailers = {
428
430
  ANAS_ALRASHEED: f'former information minister of Kuwait {QUESTION_MARKS}',
429
431
  AZIZA_ALAHMADI: 'Abu Dhabi Department of Culture & Tourism',
@@ -2,6 +2,7 @@ import json
2
2
 
3
3
  from rich.padding import Padding
4
4
 
5
+ from epstein_files.documents.document import Document
5
6
  from epstein_files.documents.email import Email
6
7
  from epstein_files.documents.messenger_log import MessengerLog
7
8
  from epstein_files.documents.other_file import FIRST_FEW_LINES, OtherFile
@@ -10,6 +11,7 @@ from epstein_files.util.constant import output_files
10
11
  from epstein_files.util.constant.html import *
11
12
  from epstein_files.util.constant.names import *
12
13
  from epstein_files.util.constant.output_files import JSON_FILES_JSON_PATH, JSON_METADATA_PATH
14
+ from epstein_files.util.constant.strings import TIMESTAMP_DIM
13
15
  from epstein_files.util.data import dict_sets_to_lists
14
16
  from epstein_files.util.env import args
15
17
  from epstein_files.util.file_helper import log_file_write
@@ -27,8 +29,8 @@ DEFAULT_EMAILERS = [
27
29
  AL_SECKEL,
28
30
  DANIEL_SIAD,
29
31
  JEAN_LUC_BRUNEL,
30
- STEVEN_HOFFENBERG,
31
32
  RENATA_BOLOTOVA,
33
+ STEVEN_HOFFENBERG,
32
34
  MASHA_DROKOVA,
33
35
  EHUD_BARAK,
34
36
  MARTIN_NOWAK,
@@ -92,7 +94,7 @@ def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
92
94
  num_emails_printed_since_last_color_key = 0
93
95
 
94
96
  if emailer_tables:
95
- print_author_header(f"Email Tables for {len(emailer_tables)} Other People", 'white')
97
+ print_author_panel(f"Email Tables for {len(emailer_tables)} Other People", 'white')
96
98
 
97
99
  for name in DEFAULT_EMAILER_TABLES:
98
100
  epstein_files.print_emails_table_for(name)
@@ -169,6 +171,32 @@ def print_text_messages_section(epstein_files: EpsteinFiles) -> None:
169
171
  print_centered(MessengerLog.summary_table(epstein_files.imessage_logs))
170
172
 
171
173
 
174
+ def write_complete_emails_timeline(epstein_files: EpsteinFiles) -> None:
175
+ table = build_table('All Non-Junk Emails In Chronological Order', highlight=True)
176
+ table.add_column('ID', style='dim')
177
+ table.add_column('Sent At', style=TIMESTAMP_DIM)
178
+ table.add_column('Author', max_width=22)
179
+ table.add_column('Recipients', max_width=30)
180
+ table.add_column('Length', justify='right', style='wheat4')
181
+ table.add_column('Subject')
182
+
183
+ for email in Document.sort_by_timestamp(epstein_files.non_duplicate_emails()):
184
+ if email.is_junk_mail():
185
+ continue
186
+
187
+ table.add_row(
188
+ email.source_file_id(),
189
+ email.epstein_media_link(link_txt=email.timestamp_without_seconds()),
190
+ email.author_txt,
191
+ email.recipients_txt(max_full_names=1),
192
+ f"{email.length()}",
193
+ email.subject(),
194
+ )
195
+
196
+ console.line(2)
197
+ console.print(table)
198
+
199
+
172
200
  def write_json_metadata(epstein_files: EpsteinFiles) -> None:
173
201
  json_str = epstein_files.json_metadata()
174
202
 
@@ -14,8 +14,7 @@ from rich.theme import Theme
14
14
 
15
15
  from epstein_files.util.constant.html import CONSOLE_HTML_FORMAT, HTML_TERMINAL_THEME, PAGE_TITLE
16
16
  from epstein_files.util.constant.names import UNKNOWN
17
- from epstein_files.util.constant.output_files import SITE_URLS
18
- from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, QUESTION_MARKS, TEXT_MESSAGE, SiteType
17
+ from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, QUESTION_MARKS, TEXT_MESSAGE
19
18
  from epstein_files.util.constant.urls import *
20
19
  from epstein_files.util.constants import FALLBACK_TIMESTAMP, HEADER_ABBREVIATIONS
21
20
  from epstein_files.util.data import json_safe
@@ -143,7 +142,8 @@ def parenthesize(msg: str | Text, style: str = '') -> Text:
143
142
  return Text('(', style=style).append(txt).append(')')
144
143
 
145
144
 
146
- def print_author_header(msg: str, color: str | None, footer: str | None = None) -> None:
145
+ def print_author_panel(msg: str, color: str | None, footer: str | None = None) -> None:
146
+ """Print a panel with the name of an emailer and a few tidbits of information about them."""
147
147
  txt = Text(msg, justify='center')
148
148
  color = color or 'white'
149
149
  color = 'white' if color == DEFAULT else color
@@ -180,26 +180,32 @@ def print_color_key() -> None:
180
180
  print_centered(vertically_pad(color_table))
181
181
 
182
182
 
183
- def print_header(epstein_files: 'EpsteinFiles') -> None:
184
- not_optimized_msg = f"This site isn't optimized for mobile"
183
+ def print_title_page_header(epstein_files: 'EpsteinFiles') -> None:
184
+ not_optimized_msg = f"This page isn't optimized for mobile"
185
185
 
186
186
  if not args.all_emails:
187
187
  not_optimized_msg += f" but if you get past the header it should be readable"
188
188
 
189
189
  console.print(f"{not_optimized_msg}.\n", style='dim')
190
190
  print_page_title(width=TITLE_WIDTH)
191
- site_type = EMAIL if args.all_emails else TEXT_MESSAGE
192
- print_starred_header(f"This is the Epstein {site_type.title()}s site", num_spaces=4, num_stars=14)
193
- other_site_msg = "another site with" + (' all of' if other_site_type() == EMAIL else '')
194
- other_site_msg += f" Epstein's {other_site_type}s also generated by this code"
195
- other_site_link_markup = link_markup(other_site_url(), other_site_msg, OTHER_SITE_LINK_STYLE)
196
- print_centered(parenthesize(Text.from_markup(other_site_link_markup)), style='bold')
197
- word_count_link = link_text_obj(WORD_COUNT_URL, 'most frequently used words in the emails and texts', AUX_SITE_LINK_STYLE)
198
- print_centered(parenthesize(word_count_link))
199
- metadata_link = link_text_obj(JSON_METADATA_URL, 'author attribution explanations', AUX_SITE_LINK_STYLE)
200
- print_centered(parenthesize(metadata_link))
201
- json_link = link_text_obj(WORD_COUNT_URL, "epstein's json files", AUX_SITE_LINK_STYLE)
202
- print_centered(parenthesize(json_link))
191
+ site_type = EMAIL if (args.all_emails or args.email_timeline) else TEXT_MESSAGE
192
+ title = f"This is the " + ('chronological ' if args.email_timeline else '') + f"Epstein {site_type.title()}s page"
193
+ print_starred_header(title, num_spaces=4, num_stars=14)
194
+ other_site_msg = "another page with" + (' all of' if other_site_type() == EMAIL else '')
195
+ other_site_msg += f" Epstein's {other_site_type()}s also generated by this code"
196
+
197
+ links = [
198
+ Text.from_markup(link_markup(other_site_url(), other_site_msg, f"{OTHER_SITE_LINK_STYLE} bold")),
199
+ link_text_obj(WORD_COUNT_URL, 'most frequently used words in the emails and texts', AUX_SITE_LINK_STYLE),
200
+ link_text_obj(JSON_METADATA_URL, 'author attribution explanations', AUX_SITE_LINK_STYLE),
201
+ link_text_obj(WORD_COUNT_URL, "epstein's json files", AUX_SITE_LINK_STYLE),
202
+ ]
203
+
204
+ for link in links:
205
+ print_centered(parenthesize(link))
206
+
207
+
208
+ def print_title_page_tables(epstein_files: 'EpsteinFiles') -> None:
203
209
  _print_external_links()
204
210
  console.line()
205
211
  _print_abbreviations_table()
@@ -274,6 +280,10 @@ def print_other_page_link(epstein_files: 'EpsteinFiles') -> None:
274
280
  txt.append(" unclassifiable files of particular interest")
275
281
 
276
282
  print_centered(parenthesize(txt), style='dim')
283
+ chrono_emails_markup = link_text_obj(CHRONOLOGICAL_EMAILS_URL, 'a page', style='light_slate_grey bold')
284
+ chrono_emails_txt = Text(f"there's also ").append(chrono_emails_markup)
285
+ chrono_emails_txt.append(' with a table of all the emails in chronological order')
286
+ print_centered(parenthesize(chrono_emails_txt), style='dim')
277
287
 
278
288
 
279
289
  def print_page_title(expand: bool = True, width: int | None = None) -> None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epstein-files
3
- Version: 1.0.16
3
+ Version: 1.1.0
4
4
  Summary: Tools for working with the Jeffrey Epstein documents released in November 2025.
5
5
  Home-page: https://michelcrypt4d4mus.github.io/epstein_text_messages/
6
6
  License: GPL-3.0-or-later
@@ -1,33 +1,33 @@
1
- epstein_files/__init__.py,sha256=nOJ26LghS0sCdQ4qGwklVm18kbuFRn6Uu1-JDE8jwd4,5050
1
+ epstein_files/__init__.py,sha256=Roo3XeVQXkgMOQMdq74-dBdkRGeY3sAv7LihZwaGvaY,5538
2
2
  epstein_files/documents/communication.py,sha256=oqNsSDWe-N0jSmchIHxpihihzIWha-foFqMwKZlxyng,2057
3
- epstein_files/documents/document.py,sha256=5GdM8grhaaefmXeV26XwYRZvMNqFETFnE7mFpVGGNik,17249
4
- epstein_files/documents/email.py,sha256=fp8hwXoBvc0xMXoddOBFQZLjzUNwKxHdnRnk0htsQ-g,41186
3
+ epstein_files/documents/document.py,sha256=s77ZQwwYmm-PDc2rZqg01FY4hpBh-XF5OHcgTVZZBes,17395
4
+ epstein_files/documents/email.py,sha256=rGnJjRzLnq70jZpWU3oslNW-FhztlcszS3v7mwEcmzY,41222
5
5
  epstein_files/documents/emails/email_header.py,sha256=wkPfSLbmzkAeQwvhf0bAeFDLPbQT-EeG0v8vNNLYktM,7502
6
6
  epstein_files/documents/imessage/text_message.py,sha256=icIiKRRuZapkV9r_PID_7hEfy7YvPrIm9Emc4QiYxbw,2806
7
7
  epstein_files/documents/json_file.py,sha256=WcZW5NNqA67rHTdopbOGtup00muNaLlvrNgKb-K4zO8,1504
8
8
  epstein_files/documents/messenger_log.py,sha256=zRVVe82cNmNmg50kqScH38yGGoSx8NjCWfLwFGUb0rs,6501
9
9
  epstein_files/documents/other_file.py,sha256=CCwOYsipYTWZj8pSTl0kgUy_LRu7Z5ZuWygQEYhilNA,9778
10
- epstein_files/epstein_files.py,sha256=LHApXzJBKauK4Z8Bn00FPr4A83VTdITnGqLn0W_vpnQ,17883
10
+ epstein_files/epstein_files.py,sha256=LsvD9O1YNp9xFIVOrswOfnbfafxgbS9ve9cqSfQENy8,17881
11
11
  epstein_files/util/constant/common_words.py,sha256=aR0UjoWmxyR49XS-DtHECQ1CiA_bK8hNP6CQ1TS9yZA,3696
12
- epstein_files/util/constant/html.py,sha256=9U098TGzlghGg4WfxLYHyub5JGR17Dv7VP5i2MSu8Kk,1415
13
- epstein_files/util/constant/names.py,sha256=M9sPZwjK1G4Jii4HJpWpUa5Y6lZ7CSJAMqRdpg5YdQg,10505
14
- epstein_files/util/constant/output_files.py,sha256=BkV4_gmdj46RfGy5SFYp6dgTty3FtlBth5YGmaGutls,1700
12
+ epstein_files/util/constant/html.py,sha256=ebBgoUAnKnOGSK-kMF-ybvc_2sUgAuJKiH1L4rx5kYY,1526
13
+ epstein_files/util/constant/names.py,sha256=ceFaok_1azCzTvlsE3t-urYiKu7YNc8PumwXpkE3dmc,10513
14
+ epstein_files/util/constant/output_files.py,sha256=et1y3AzkxKqK0k-wDhMEsQFMfoXrUpoJCn6nQONurkU,1911
15
15
  epstein_files/util/constant/strings.py,sha256=rOKmYrGIZFPQbhCgd8z7mUF0CbQQBO4ij8kXvGTDkoE,1857
16
16
  epstein_files/util/constant/urls.py,sha256=-cylNL7xJi18a6fawVTF8MO3oEdjMvo9Hg1MwC-7ydI,5098
17
17
  epstein_files/util/constants.py,sha256=4WABkEBshduSV4cZkxv0MwgJci9g6MxjhtJfPjersEo,112495
18
18
  epstein_files/util/data.py,sha256=JccGFZGiCGm7XtwpQTocIjGYOr6hTUpEPwHhjyW9Xnc,3164
19
19
  epstein_files/util/doc_cfg.py,sha256=aBIm0hyxf-aeMsb8ZUNiQFVsPFimjVUIkrVdDrg1iQU,9105
20
- epstein_files/util/env.py,sha256=aSUC_TwdtKB8z6PCDaLeu3k5qZE3AgcsP9jU2p9eaP0,5640
20
+ epstein_files/util/env.py,sha256=AWZYDHQ6JRHobY1o7BHRbVObYpBejhsUN4TrOyZbzgQ,5783
21
21
  epstein_files/util/file_helper.py,sha256=uJzNHihuAi2-XMNbUdjdovgppcIt-fVIIrXHXVShA6Q,2904
22
- epstein_files/util/highlighted_group.py,sha256=Tm4Fy7oZv3PLWW1yCaRCY2NDfr2RM8zB9sudvqw11lw,36746
22
+ epstein_files/util/highlighted_group.py,sha256=4r-asWz53zVHKckONxTOluFCRqbX92nAID1gdXqf08g,37047
23
23
  epstein_files/util/logging.py,sha256=fuREq06xUUI3DfCV2JE-8QM-sQKxpLDj0_AYFO6qR1M,1983
24
- epstein_files/util/output.py,sha256=hnWK0YHEGC5GSlcQA80ZpxUnXV42S0ytKDARl1qk-0E,9012
25
- epstein_files/util/rich.py,sha256=13sPSve9UEqo51t_5hf86Z492ju4OCHWMxrz6r1PnNQ,15338
24
+ epstein_files/util/output.py,sha256=WK37ZbTvhPHczH8GNly2Pjw9IwAsiF_yn43u7Q2jf7k,10051
25
+ epstein_files/util/rich.py,sha256=oic1wcqipTSj9G1YYP8HTzsJvGeIgrK8vN95uGOJ8sU,15715
26
26
  epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
27
27
  epstein_files/util/timer.py,sha256=8hxW4Y1JcTUfnBrHh7sL2pM9xu1sL4HFQM4CmmzTarU,837
28
28
  epstein_files/util/word_count.py,sha256=o_-HnfzHdPDPR8oA_dv6fjy1dbsHee8p_aoe62PEQHw,9213
29
- epstein_files-1.0.16.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
30
- epstein_files-1.0.16.dist-info/METADATA,sha256=_tOJD6MZxx4QAja1cVCNOn61_5675M6DTtvFoXQx9UI,5867
31
- epstein_files-1.0.16.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
32
- epstein_files-1.0.16.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
33
- epstein_files-1.0.16.dist-info/RECORD,,
29
+ epstein_files-1.1.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
30
+ epstein_files-1.1.0.dist-info/METADATA,sha256=X3RI8102moyBUsT1h6TXPrNdMp6uSokA_d-g5x2GGAA,5866
31
+ epstein_files-1.1.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
32
+ epstein_files-1.1.0.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
33
+ epstein_files-1.1.0.dist-info/RECORD,,