epstein-files 1.1.0__py3-none-any.whl → 1.1.2__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.
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from os import environ
3
+ from sys import exit
3
4
 
4
5
  import datefinder
5
6
  import rich_argparse_plus
@@ -39,7 +40,7 @@ class LogHighlighter(ReprHighlighter):
39
40
 
40
41
  log_console = Console(color_system='256', theme=Theme(LOG_THEME))
41
42
  log_handler = RichHandler(console=log_console, highlighter=LogHighlighter())
42
- logging.basicConfig(level="NOTSET", format="%(message)s", datefmt="[%X]", handlers=[log_handler])
43
+ logging.basicConfig(level="NOTSET", format="%(message)s", datefmt=" ", handlers=[log_handler])
43
44
  logger = logging.getLogger("rich")
44
45
 
45
46
 
@@ -58,3 +59,9 @@ if env_log_level_str:
58
59
 
59
60
  logger.warning(f"Setting log level to {env_log_level} based on {LOG_LEVEL_ENV_VAR} env var...")
60
61
  logger.setLevel(env_log_level)
62
+
63
+
64
+ def exit_with_error(msg: str) -> None:
65
+ print('')
66
+ logger.error(msg + '\n')
67
+ exit(1)
@@ -3,7 +3,7 @@ import json
3
3
  from rich.padding import Padding
4
4
 
5
5
  from epstein_files.documents.document import Document
6
- from epstein_files.documents.email import Email
6
+ from epstein_files.documents.email import JUNK_EMAILERS, KRASSNER_RECIPIENTS, Email
7
7
  from epstein_files.documents.messenger_log import MessengerLog
8
8
  from epstein_files.documents.other_file import FIRST_FEW_LINES, OtherFile
9
9
  from epstein_files.epstein_files import EpsteinFiles, count_by_month
@@ -11,8 +11,8 @@ from epstein_files.util.constant import output_files
11
11
  from epstein_files.util.constant.html import *
12
12
  from epstein_files.util.constant.names import *
13
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
15
- from epstein_files.util.data import dict_sets_to_lists
14
+ from epstein_files.util.constant.strings import TIMESTAMP_DIM, TIMESTAMP_STYLE
15
+ from epstein_files.util.data import dict_sets_to_lists, sort_dict
16
16
  from epstein_files.util.env import args
17
17
  from epstein_files.util.file_helper import log_file_write
18
18
  from epstein_files.util.logging import logger
@@ -35,53 +35,43 @@ DEFAULT_EMAILERS = [
35
35
  EHUD_BARAK,
36
36
  MARTIN_NOWAK,
37
37
  STEVE_BANNON,
38
+ TYLER_SHEARS,
38
39
  JIDE_ZEITLIN,
40
+ CHRISTINA_GALBRAITH,
39
41
  DAVID_STERN,
40
42
  MOHAMED_WAHEED_HASSAN,
41
43
  JENNIFER_JACQUET,
42
- TYLER_SHEARS,
43
- CHRISTINA_GALBRAITH,
44
44
  ZUBAIR_KHAN,
45
45
  None,
46
46
  ]
47
47
 
48
- # Order matters. Default names to print tables w/email subject, timestamp, etc for. # TODO: get rid of this ?
49
- DEFAULT_EMAILER_TABLES: list[str | None] = [
50
- GHISLAINE_MAXWELL,
51
- PRINCE_ANDREW,
52
- SULTAN_BIN_SULAYEM,
53
- ARIANE_DE_ROTHSCHILD,
48
+ INVALID_FOR_EPSTEIN_WEB = JUNK_EMAILERS + KRASSNER_RECIPIENTS + [
49
+ 'ACT for America',
50
+ 'BS Stern',
51
+ INTELLIGENCE_SQUARED,
52
+ UNKNOWN,
54
53
  ]
55
54
 
56
- if len(set(DEFAULT_EMAILERS).intersection(set(DEFAULT_EMAILER_TABLES))) > 0:
57
- raise RuntimeError(f"Some names appear in both DEFAULT_EMAILERS and DEFAULT_EMAILER_TABLES")
58
-
59
55
 
60
56
  def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
61
57
  """Returns emails that were printed (may contain dupes if printed for both author and recipient)."""
62
58
  print_section_header(('Selections from ' if not args.all_emails else '') + 'His Emails')
63
- print_other_page_link(epstein_files)
64
59
  emailers_to_print: list[str | None]
65
- emailer_tables: list[str | None] = []
66
60
  already_printed_emails: list[Email] = []
67
61
  num_emails_printed_since_last_color_key = 0
68
62
 
69
63
  if args.names:
70
64
  emailers_to_print = args.names
71
65
  else:
72
- print_centered(Padding(epstein_files.table_of_emailers(), (2, 0)))
73
-
74
66
  if args.all_emails:
75
67
  emailers_to_print = sorted(epstein_files.all_emailers(), key=lambda e: epstein_files.earliest_email_at(e))
76
- console.print('Email conversations are sorted chronologically based on time of the first email.')
77
- print_numbered_list_of_emailers(emailers_to_print, epstein_files)
78
68
  else:
79
69
  emailers_to_print = DEFAULT_EMAILERS
80
- emailer_tables = DEFAULT_EMAILER_TABLES
81
- console.print('Email conversations grouped by counterparty can be found in the order listed below.')
82
- print_numbered_list_of_emailers(emailers_to_print)
83
- console.print("\nAfter that there's tables linking to (but not displaying) all known emails for each of these people:")
84
- print_numbered_list_of_emailers(emailer_tables)
70
+
71
+ print_other_page_link(epstein_files)
72
+ console.line(2)
73
+ console.print(table_of_selected_emailers(emailers_to_print, epstein_files))
74
+ console.print(Padding(_all_emailers_table(epstein_files), (2, 0)))
85
75
 
86
76
  for author in emailers_to_print:
87
77
  author_emails = epstein_files.print_emails_for(author)
@@ -93,12 +83,6 @@ def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
93
83
  print_color_key()
94
84
  num_emails_printed_since_last_color_key = 0
95
85
 
96
- if emailer_tables:
97
- print_author_panel(f"Email Tables for {len(emailer_tables)} Other People", 'white')
98
-
99
- for name in DEFAULT_EMAILER_TABLES:
100
- epstein_files.print_emails_table_for(name)
101
-
102
86
  if not args.names:
103
87
  epstein_files.print_email_device_info()
104
88
 
@@ -140,54 +124,99 @@ def print_json_stats(epstein_files: EpsteinFiles) -> None:
140
124
 
141
125
  def print_other_files_section(files: list[OtherFile], epstein_files: EpsteinFiles) -> None:
142
126
  """Returns the OtherFile objects that were interesting enough to print."""
143
- category_table = OtherFile.count_by_category_table(files)
144
- other_files_preview_table = OtherFile.files_preview_table(files)
145
- header_pfx = '' if args.all_other_files else 'Selected '
146
- print_section_header(f"{FIRST_FEW_LINES} of {len(files)} {header_pfx}Files That Are Neither Emails Nor Text Messages")
147
-
148
- if args.all_other_files:
149
- console.line(1)
150
- else:
151
- print_other_page_link(epstein_files)
152
- console.line(2)
153
-
154
- for table in [category_table, other_files_preview_table]:
155
- table.title = f"{header_pfx}{table.title}"
156
-
157
- print_centered(category_table)
158
- console.line(2)
127
+ title_pfx = '' if args.all_other_files else 'Selected '
128
+ category_table = OtherFile.count_by_category_table(files, title_pfx=title_pfx)
129
+ other_files_preview_table = OtherFile.files_preview_table(files, title_pfx=title_pfx)
130
+ print_section_header(f"{FIRST_FEW_LINES} of {len(files)} {title_pfx}Files That Are Neither Emails Nor Text Messages")
131
+ print_other_page_link(epstein_files)
132
+ print_centered(Padding(category_table, (2, 0)))
159
133
  console.print(other_files_preview_table)
160
134
 
161
135
 
162
- def print_text_messages_section(epstein_files: EpsteinFiles) -> None:
136
+ def print_text_messages_section(imessage_logs: list[MessengerLog]) -> None:
163
137
  """Print summary table and stats for text messages."""
138
+ if not imessage_logs:
139
+ logger.warning(f"No MessengerLog objects to output...")
140
+ return
141
+
164
142
  print_section_header('All of His Text Messages')
165
- print_centered("(conversations are sorted chronologically based on timestamp of first message)\n", style='gray30')
143
+ print_centered("(conversations are sorted chronologically based on timestamp of first message in the log file)", style='dim')
144
+ console.line(2)
166
145
 
167
- for log_file in epstein_files.imessage_logs:
146
+ if not args.names:
147
+ print_centered(MessengerLog.summary_table(imessage_logs))
148
+ console.line(2)
149
+
150
+ for log_file in imessage_logs:
168
151
  console.print(Padding(log_file))
169
152
  console.line(2)
170
153
 
171
- print_centered(MessengerLog.summary_table(epstein_files.imessage_logs))
154
+
155
+ def table_of_selected_emailers(_list: list[str | None], epstein_files: EpsteinFiles) -> Table:
156
+ """Add the first emailed_at timestamp for each emailer if 'epstein_files' provided."""
157
+ header_pfx = '' if args.all_emails else 'Selected '
158
+ table = build_table(f'{header_pfx}Email Conversations Grouped by Counterparty Will Appear in this Order')
159
+ table.add_column('Start Date')
160
+ table.add_column('Name', max_width=25, no_wrap=True)
161
+ table.add_column('Category', justify='center', style='dim italic')
162
+ table.add_column('Num', justify='right', style='wheat4')
163
+ table.add_column('Info', style='white italic')
164
+ current_year = 1990
165
+ current_year_month = current_year * 12
166
+ grey_idx = 0
167
+
168
+ for i, name in enumerate(_list):
169
+ earliest_email_date = (epstein_files.earliest_email_at(name) or FALLBACK_TIMESTAMP).date()
170
+ year_months = (earliest_email_date.year * 12) + earliest_email_date.month
171
+
172
+ # Color year rollovers more brightly
173
+ if current_year != earliest_email_date.year:
174
+ grey_idx = 0
175
+ elif current_year_month != year_months:
176
+ grey_idx = ((current_year_month - 1) % 12) + 1
177
+
178
+ current_year_month = year_months
179
+ current_year = earliest_email_date.year
180
+ category = get_category_for_name(name)
181
+ info = get_info_for_name(name)
182
+
183
+ if category and category.plain == 'paula_heil_fisher': # TODO: hacky
184
+ category = None
185
+ elif category and info:
186
+ info = info.removeprefix(f"{category.plain}, ")
187
+ elif not name:
188
+ info = Text('(emails whose author or recipient could not be determined)', style='medium_purple4')
189
+
190
+ table.add_row(
191
+ Text(str(earliest_email_date), style=f"grey{GREY_NUMBERS[grey_idx]}"),
192
+ Text(name or UNKNOWN, style=get_style_for_name(name or UNKNOWN, default_style='dim')),
193
+ category,
194
+ f"{len(epstein_files.emails_for(name)):,}",
195
+ info or '',
196
+ )
197
+
198
+ return table
172
199
 
173
200
 
174
201
  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)
202
+ """Print a table of all emails in chronological order."""
203
+ emails = [email for email in epstein_files.non_duplicate_emails() if not email.is_junk_mail()]
204
+ table = build_table(f'All {len(emails):,} Non-Junk Emails in Chronological Order', highlight=True)
205
+ table.add_column('ID', style=TIMESTAMP_DIM)
206
+ table.add_column('Sent At', style='dim')
207
+ table.add_column('Author', max_width=20)
208
+ table.add_column('Recipients', max_width=22)
180
209
  table.add_column('Length', justify='right', style='wheat4')
181
210
  table.add_column('Subject')
182
211
 
183
- for email in Document.sort_by_timestamp(epstein_files.non_duplicate_emails()):
212
+ for email in Document.sort_by_timestamp(emails):
184
213
  if email.is_junk_mail():
185
214
  continue
186
215
 
187
216
  table.add_row(
188
- email.source_file_id(),
189
- email.epstein_media_link(link_txt=email.timestamp_without_seconds()),
190
- email.author_txt,
217
+ email.epstein_media_link(link_txt=email.source_file_id()),
218
+ email.timestamp_without_seconds(),
219
+ email.author_txt(),
191
220
  email.recipients_txt(max_full_names=1),
192
221
  f"{email.length()}",
193
222
  email.subject(),
@@ -230,6 +259,64 @@ def write_urls() -> None:
230
259
  logger.warning(f"Wrote {len(url_vars)} URL variables to '{URLS_ENV}'\n")
231
260
 
232
261
 
262
+ def _all_emailers_table(epstein_files: EpsteinFiles) -> Table:
263
+ attributed_emails = [e for e in epstein_files.non_duplicate_emails() if e.author]
264
+ footer = f"(identified {len(epstein_files.email_author_counts)} authors of {len(attributed_emails):,}"
265
+ footer = f"{footer} out of {len(epstein_files.non_duplicate_emails()):,} emails)"
266
+ counts_table = build_table("All of the Email Counterparties Who Appear in the Files", caption=footer)
267
+
268
+ add_cols_to_table(counts_table, [
269
+ 'Name',
270
+ {'name': 'Count', 'justify': 'right', 'style': 'bold bright_white'},
271
+ {'name': 'Sent', 'justify': 'right', 'style': 'gray74'},
272
+ {'name': 'Recv', 'justify': 'right', 'style': 'gray74'},
273
+ {'name': 'First', 'style': TIMESTAMP_STYLE},
274
+ {'name': 'Last', 'style': LAST_TIMESTAMP_STYLE},
275
+ {'name': 'Days', 'justify': 'right', 'style': 'dim'},
276
+ JMAIL,
277
+ EPSTEIN_MEDIA,
278
+ EPSTEIN_WEB,
279
+ 'Twitter',
280
+ ])
281
+
282
+ emailer_counts = {
283
+ emailer: epstein_files.email_author_counts[emailer] + epstein_files.email_recipient_counts[emailer]
284
+ for emailer in epstein_files.all_emailers(True)
285
+ }
286
+
287
+ for name, count in sort_dict(emailer_counts):
288
+ style = get_style_for_name(name, default_style=DEFAULT_NAME_STYLE)
289
+ emails = epstein_files.emails_for(name)
290
+
291
+ counts_table.add_row(
292
+ Text.from_markup(link_markup(epsteinify_name_url(name or UNKNOWN), name or UNKNOWN, style)),
293
+ f"{count:,}",
294
+ str(epstein_files.email_author_counts[name]),
295
+ str(epstein_files.email_recipient_counts[name]),
296
+ emails[0].date_str(),
297
+ emails[-1].date_str(),
298
+ f"{epstein_files.email_conversation_length_in_days(name)}",
299
+ link_text_obj(search_jmail_url(name), JMAIL) if name else '',
300
+ link_text_obj(epstein_media_person_url(name), EPSTEIN_MEDIA) if _is_ok_for_epstein_web(name) else '',
301
+ link_text_obj(epstein_web_person_url(name), EPSTEIN_WEB) if _is_ok_for_epstein_web(name) else '',
302
+ link_text_obj(search_twitter_url(name), 'search X') if name else '',
303
+ )
304
+
305
+ return counts_table
306
+
307
+
308
+ def _is_ok_for_epstein_web(name: str | None) -> bool:
309
+ """Return True if it's likely that EpsteinWeb has a page for this name."""
310
+ if name is None or ' ' not in name:
311
+ return False
312
+ elif '@' in name or '/' in name or '??' in name:
313
+ return False
314
+ elif name in INVALID_FOR_EPSTEIN_WEB:
315
+ return False
316
+
317
+ return True
318
+
319
+
233
320
  def _verify_all_emails_were_printed(epstein_files: EpsteinFiles, already_printed_emails: list[Email]) -> None:
234
321
  """Log warnings if some emails were never printed."""
235
322
  email_ids_that_were_printed = set([email.file_id for email in already_printed_emails])
@@ -20,25 +20,27 @@ from epstein_files.util.constants import FALLBACK_TIMESTAMP, HEADER_ABBREVIATION
20
20
  from epstein_files.util.data import json_safe
21
21
  from epstein_files.util.env import args
22
22
  from epstein_files.util.file_helper import log_file_write
23
- from epstein_files.util.highlighted_group import ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter
23
+ from epstein_files.util.highlighted_group import (ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter,
24
+ get_category_for_name, get_info_for_name, get_style_for_name)
24
25
  from epstein_files.util.logging import logger
25
26
 
26
27
  TITLE_WIDTH = 50
28
+ MIN_AUTHOR_PANEL_WIDTH = 80
27
29
  NUM_COLOR_KEY_COLS = 4
28
30
  NA_TXT = Text(NA, style='dim')
29
- QUESTION_MARK_TXT = Text(QUESTION_MARKS, style='dim')
30
31
  GREY_NUMBERS = [58, 39, 39, 35, 30, 27, 23, 23, 19, 19, 15, 15, 15]
31
32
 
32
33
  DEFAULT_NAME_STYLE = 'gray46'
33
34
  INFO_STYLE = 'white dim italic'
34
- KEY_STYLE='honeydew2 bold'
35
- LAST_TIMESTAMP_STYLE='wheat4'
35
+ KEY_STYLE = 'honeydew2 bold'
36
+ LAST_TIMESTAMP_STYLE = 'wheat4'
37
+ OTHER_PAGE_MSG_STYLE = 'gray78 dim'
36
38
  SECTION_HEADER_STYLE = 'bold white on blue3'
37
39
  SOCIAL_MEDIA_LINK_STYLE = 'pale_turquoise4'
38
40
  SUBSTACK_POST_LINK_STYLE = 'bright_cyan'
39
41
  SYMBOL_STYLE = 'grey70'
40
42
  TABLE_BORDER_STYLE = 'grey46'
41
- TABLE_TITLE_STYLE = f"gray85 italic"
43
+ TABLE_TITLE_STYLE = f"gray54 italic"
42
44
  TITLE_STYLE = 'black on bright_white bold'
43
45
 
44
46
  AUX_SITE_LINK_STYLE = 'dark_orange3'
@@ -46,6 +48,7 @@ OTHER_SITE_LINK_STYLE = 'dark_goldenrod'
46
48
 
47
49
  DEFAULT_TABLE_KWARGS = {
48
50
  'border_style': TABLE_BORDER_STYLE,
51
+ 'caption_style': 'navajo_white3 dim italic',
49
52
  'header_style': "bold",
50
53
  'title_style': TABLE_TITLE_STYLE,
51
54
  }
@@ -82,15 +85,21 @@ highlighter = CONSOLE_ARGS['highlighter']
82
85
  def add_cols_to_table(table: Table, col_names: list[str | dict]) -> None:
83
86
  """Left most col will be left justified, rest are center justified."""
84
87
  for i, col in enumerate(col_names):
88
+ justify='left' if i == 0 else 'center'
89
+
85
90
  if isinstance(col, dict):
86
91
  col_name = col['name']
87
92
  kwargs = col
88
93
  del kwargs['name']
94
+
95
+ if 'justify' in col:
96
+ justify = col['justify']
97
+ del col['justify']
89
98
  else:
90
99
  col_name = col
91
100
  kwargs = {}
92
101
 
93
- table.add_column(col_name, justify='left' if i == 0 else 'center', **kwargs)
102
+ table.add_column(col_name, justify=justify, **kwargs)
94
103
 
95
104
 
96
105
  def build_highlighter(pattern: str) -> EpsteinHighlighter:
@@ -144,10 +153,9 @@ def parenthesize(msg: str | Text, style: str = '') -> Text:
144
153
 
145
154
  def print_author_panel(msg: str, color: str | None, footer: str | None = None) -> None:
146
155
  """Print a panel with the name of an emailer and a few tidbits of information about them."""
147
- txt = Text(msg, justify='center')
148
- color = color or 'white'
149
- color = 'white' if color == DEFAULT else color
150
- panel = Panel(txt, width=80, style=f"black on {color} bold")
156
+ color = 'white' if (not color or color == DEFAULT) else color
157
+ width = max(MIN_AUTHOR_PANEL_WIDTH, len(msg) + 4)
158
+ panel = Panel(Text(msg, justify='center'), width=width, style=f"black on {color} bold")
151
159
  console.print('\n', Align.center(panel))
152
160
 
153
161
  if footer:
@@ -181,16 +189,11 @@ def print_color_key() -> None:
181
189
 
182
190
 
183
191
  def print_title_page_header(epstein_files: 'EpsteinFiles') -> None:
184
- not_optimized_msg = f"This page isn't optimized for mobile"
185
-
186
- if not args.all_emails:
187
- not_optimized_msg += f" but if you get past the header it should be readable"
188
-
189
- console.print(f"{not_optimized_msg}.\n", style='dim')
190
192
  print_page_title(width=TITLE_WIDTH)
191
193
  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
+ title = f"This is the " + ('chronological ' if args.email_timeline else '') + f"Epstein {site_type.title()}s Page"
195
+ print_starred_header(title, num_spaces=9 if args.all_emails else 6, num_stars=14)
196
+ print_centered(f"These documents come from the Nov. 2025 House Oversight Committee release.\n", style='gray74')
194
197
  other_site_msg = "another page with" + (' all of' if other_site_type() == EMAIL else '')
195
198
  other_site_msg += f" Epstein's {other_site_type()}s also generated by this code"
196
199
 
@@ -218,8 +221,6 @@ def print_title_page_tables(epstein_files: 'EpsteinFiles') -> None:
218
221
 
219
222
 
220
223
  def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
221
- print(obj)
222
-
223
224
  if isinstance(obj, dict):
224
225
  if skip_falsey:
225
226
  obj = {k: v for k, v in obj.items() if v}
@@ -232,68 +233,33 @@ def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
232
233
  console.line()
233
234
 
234
235
 
235
- def print_numbered_list_of_emailers(_list: list[str | None], epstein_files = None) -> None:
236
- """Add the first emailed_at timestamp for each emailer if 'epstein_files' provided."""
237
- current_year = 1990
238
- current_year_month = current_year * 12
239
- grey_idx = 0
240
- console.line()
241
-
242
- for i, name in enumerate(_list):
243
- indent = ' ' if i < 9 else (' ' if i < 99 else ' ')
244
- txt = Text((indent) + F" {i + 1}. ", style=DEFAULT_NAME_STYLE)
245
-
246
- if epstein_files:
247
- earliest_email_date = (epstein_files.earliest_email_at(name) or FALLBACK_TIMESTAMP).date()
248
- year_months = (earliest_email_date.year * 12) + earliest_email_date.month
249
-
250
- # Color year rollovers more brightly
251
- if current_year != earliest_email_date.year:
252
- grey_idx = 0
253
- elif current_year_month != year_months:
254
- grey_idx = ((current_year_month - 1) % 12) + 1
255
-
256
- current_year_month = year_months
257
- current_year = earliest_email_date.year
258
- txt.append(escape(f"[{earliest_email_date}] "), style=f"grey{GREY_NUMBERS[grey_idx]}")
259
-
260
- txt.append(highlighter(name or UNKNOWN))
261
-
262
- if epstein_files:
263
- num_days_in_converation = epstein_files.email_conversation_length_in_days(name)
264
- msg = f" ({len(epstein_files.emails_for(name))} emails over {num_days_in_converation:,} days)"
265
- txt.append(msg, style=f'dim italic')
266
-
267
- console.print(txt)
268
-
269
- console.line()
270
-
271
-
272
236
  def print_other_page_link(epstein_files: 'EpsteinFiles') -> None:
273
237
  markup_msg = link_markup(other_site_url(), 'the other page', style='light_slate_grey bold')
274
238
 
275
239
  if other_site_type() == EMAIL:
276
- txt = Text.from_markup(markup_msg).append(f' is uncurated and has all {len(epstein_files.other_files)}')
277
- txt.append(f" unclassifiable files and {len(epstein_files.emails):,} emails")
240
+ txt = Text.from_markup(markup_msg).append(f' is uncurated and has all {len(epstein_files.emails):,} emails')
241
+ txt.append(f" and {len(epstein_files.other_files)} unclassifiable files")
278
242
  else:
279
- txt = Text.from_markup(markup_msg).append(f' displays only a small collection of emails and')
243
+ txt = Text.from_markup(markup_msg).append(f' displays a limited collection of emails and')
280
244
  txt.append(" unclassifiable files of particular interest")
281
245
 
282
- print_centered(parenthesize(txt), style='dim')
246
+ print_centered(parenthesize(txt), style=OTHER_PAGE_MSG_STYLE)
283
247
  chrono_emails_markup = link_text_obj(CHRONOLOGICAL_EMAILS_URL, 'a page', style='light_slate_grey bold')
284
248
  chrono_emails_txt = Text(f"there's also ").append(chrono_emails_markup)
285
249
  chrono_emails_txt.append(' with a table of all the emails in chronological order')
286
- print_centered(parenthesize(chrono_emails_txt), style='dim')
250
+ print_centered(parenthesize(chrono_emails_txt), style=OTHER_PAGE_MSG_STYLE)
287
251
 
288
252
 
289
253
  def print_page_title(expand: bool = True, width: int | None = None) -> None:
254
+ warning = f"This page was generated by {link_markup('https://pypi.org/project/rich/', 'rich')}."
255
+ print_centered(f"{warning} It is not optimized for mobile.", style='dim')
290
256
  title_panel = Panel(Text(PAGE_TITLE, justify='center'), expand=expand, style=TITLE_STYLE, width=width)
291
- console.print(Align.center(vertically_pad(title_panel)))
257
+ print_centered(vertically_pad(title_panel))
292
258
  _print_social_media_links()
293
259
  console.line(2)
294
260
 
295
261
 
296
- def print_panel(msg: str, style: str = 'black on white', padding: tuple | None = None, centered: bool = False) -> None:
262
+ def print_subtitle_panel(msg: str, style: str = 'black on white', padding: tuple | None = None, centered: bool = False) -> None:
297
263
  _padding: list[int] = list(padding or [0, 0, 0, 0])
298
264
  _padding[2] += 1 # Bottom pad
299
265
  actual_padding: tuple[int, int, int, int] = tuple(_padding)
@@ -308,7 +274,7 @@ def print_panel(msg: str, style: str = 'black on white', padding: tuple | None =
308
274
  def print_section_header(msg: str, style: str = SECTION_HEADER_STYLE, is_centered: bool = False) -> None:
309
275
  panel = Panel(Text(msg, justify='center'), expand=True, padding=(1, 1), style=style)
310
276
  panel = Align.center(panel) if is_centered else panel
311
- console.print(Padding(panel, (3, 2, 1, 2)))
277
+ console.print(Padding(panel, (3, 0, 1, 0)))
312
278
 
313
279
 
314
280
  def print_starred_header(msg: str, num_stars: int = 7, num_spaces: int = 2, style: str = TITLE_STYLE) -> None:
@@ -393,5 +359,5 @@ def _print_social_media_links() -> None:
393
359
  print_centered(join_texts(social_links, join=' / '))#, encloser='()'))#, encloser='‹›'))
394
360
 
395
361
 
396
- # if args.deep_debug:
397
- # print_json('THEME_STYLES', THEME_STYLES)
362
+ if args.colors_only:
363
+ print_json('THEME_STYLES', THEME_STYLES)
@@ -11,7 +11,7 @@ class Timer:
11
11
  decimals: int = 2
12
12
 
13
13
  def print_at_checkpoint(self, msg: str) -> None:
14
- logger.warning(f"{msg} in {self.seconds_since_checkpoint_str()}")
14
+ logger.warning(f"{msg} in {self.seconds_since_checkpoint_str()}...")
15
15
  self.checkpoint_at = time.perf_counter()
16
16
 
17
17
  def seconds_since_checkpoint_str(self) -> str:
@@ -17,7 +17,7 @@ from epstein_files.util.data import ALL_NAMES, flatten, sort_dict
17
17
  from epstein_files.util.env import args
18
18
  from epstein_files.util.logging import logger
19
19
  from epstein_files.util.rich import (console, highlighter, print_centered, print_color_key, print_page_title,
20
- print_panel, print_starred_header, write_html)
20
+ print_subtitle_panel, print_starred_header, write_html)
21
21
  from epstein_files.util.search_result import MatchedLine, SearchResult
22
22
  from epstein_files.util.timer import Timer
23
23
 
@@ -196,7 +196,6 @@ def write_word_counts_html() -> None:
196
196
  epstein_files = EpsteinFiles.get_files(timer)
197
197
  email_subjects: set[str] = set()
198
198
  word_count = WordCount()
199
-
200
199
  # Remove dupes, junk mail, and fwded articles from emails
201
200
  emails = [e for e in epstein_files.non_duplicate_emails() if not (e.is_junk_mail() or e.is_fwded_article())]
202
201
 
@@ -225,7 +224,7 @@ def write_word_counts_html() -> None:
225
224
  for i, msg in enumerate(imessage_log.messages):
226
225
  if args.names and msg.author not in args.names:
227
226
  continue
228
- elif HTML_REGEX.search(line):
227
+ elif HTML_REGEX.search(msg.text):
229
228
  continue
230
229
 
231
230
  for word in msg.text.split():
@@ -239,7 +238,7 @@ def write_word_counts_html() -> None:
239
238
  console.line()
240
239
  console.print(word_count)
241
240
  console.line(2)
242
- print_panel(f"{len(COMMON_WORDS_LIST):,} Excluded Words", centered=True)
241
+ print_subtitle_panel(f"{len(COMMON_WORDS_LIST):,} Excluded Words", centered=True)
243
242
  console.print(', '.join(COMMON_WORDS_LIST), highlight=False)
244
243
  write_html(WORD_COUNT_HTML_PATH)
245
244
  timer.print_at_checkpoint(f"Finished counting words")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epstein-files
3
- Version: 1.1.0
3
+ Version: 1.1.2
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
@@ -0,0 +1,33 @@
1
+ epstein_files/__init__.py,sha256=dPSYqe6CZyXFJviCsrVLPy-uxJxG5xS_W9ZDjUih5f0,5484
2
+ epstein_files/documents/communication.py,sha256=4xcmCg4108D4Rln4tiXbm5pRBRfBGpMxcCORUCMnT6k,1908
3
+ epstein_files/documents/document.py,sha256=eQ0IgOUZiKz-KFgUwkWariQ5HqyM2VY3ZCJC4qRsnDg,17401
4
+ epstein_files/documents/email.py,sha256=b5klJTrqTydaSupC5fJkV64nDxulkWE1DAvXF8FaLCY,43369
5
+ epstein_files/documents/emails/email_header.py,sha256=wkPfSLbmzkAeQwvhf0bAeFDLPbQT-EeG0v8vNNLYktM,7502
6
+ epstein_files/documents/imessage/text_message.py,sha256=w_U2bNIKtH7rMSNP4Q0BoTDrQZ6HE2IUSFjy6rBxrgY,3348
7
+ epstein_files/documents/json_file.py,sha256=WcZW5NNqA67rHTdopbOGtup00muNaLlvrNgKb-K4zO8,1504
8
+ epstein_files/documents/messenger_log.py,sha256=pAHH8FntEyQCwoVFI3B5utSqS5LKhQrj5UD1hl3pnbg,7419
9
+ epstein_files/documents/other_file.py,sha256=TeEzsfGN_mTFZPhfyt9ihxK9oTCYwI8sRLplTsgpOMY,9893
10
+ epstein_files/epstein_files.py,sha256=K6hgDlwHFNbhdIQcc5RJN_-g_xLhNLQ1yelpzCZrZBw,15210
11
+ epstein_files/util/constant/common_words.py,sha256=C1JERPnOGHV2UMC71aEue1i9QTQog-RfT3IzdcYQOYQ,3702
12
+ epstein_files/util/constant/html.py,sha256=MFooFV8KfFBCm9hL1u6A3hi_u37i7lL6UKAYoKQj3PI,1505
13
+ epstein_files/util/constant/names.py,sha256=yHZ46IcrzOOEr6mdTllxaG_YvLrdhmJlWCwIGv4FaiU,10738
14
+ epstein_files/util/constant/output_files.py,sha256=et1y3AzkxKqK0k-wDhMEsQFMfoXrUpoJCn6nQONurkU,1911
15
+ epstein_files/util/constant/strings.py,sha256=2TLP_TWgErsXb9lF8k0lZVAAQ8QAc5ytKQi3PD9CAzY,1961
16
+ epstein_files/util/constant/urls.py,sha256=VqgqxC2IbX90yw9kfGTwAwc7VVo46TCgDVrkPy3adV4,5127
17
+ epstein_files/util/constants.py,sha256=ZllrMB7Imlp4ihviGOMxB5qU_hnbS7rP3PnjS0fVbAI,112742
18
+ epstein_files/util/data.py,sha256=JccGFZGiCGm7XtwpQTocIjGYOr6hTUpEPwHhjyW9Xnc,3164
19
+ epstein_files/util/doc_cfg.py,sha256=aBIm0hyxf-aeMsb8ZUNiQFVsPFimjVUIkrVdDrg1iQU,9105
20
+ epstein_files/util/env.py,sha256=MbS-wD0iMJyg6u-adxVGvTlXY4-ubzIjG20ovz1EMHU,6147
21
+ epstein_files/util/file_helper.py,sha256=PGPqXmt4Oz4bE45ybvaCZfI0w_PGKirTsrv7xw86gmY,2903
22
+ epstein_files/util/highlighted_group.py,sha256=Pt34bfLjkdl7VBEogeJoofXhJC9fTPHYCgb4NNDlPpI,52422
23
+ epstein_files/util/logging.py,sha256=F45YqEKAiIb0rDZnOB7XuaY-dOkOKrsfSzO1VVqY508,2097
24
+ epstein_files/util/output.py,sha256=IXBiCQYy6Z2BUGDCLeQUfJ1NUzTItjvgg-k0i4KpOzA,13751
25
+ epstein_files/util/rich.py,sha256=0hyzML8yCSF6FfjQIt7aJyEqG8unQhDOQzfd7e6FTAI,14599
26
+ epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
27
+ epstein_files/util/timer.py,sha256=QqqXAQofKPWkDngNwG0mOqRn7nHcAR-BGQjqAwZfXoE,840
28
+ epstein_files/util/word_count.py,sha256=J6aZkodXwowf09GykLgJuqwSRzrMjvefgKiM8S-T9LA,9234
29
+ epstein_files-1.1.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
30
+ epstein_files-1.1.2.dist-info/METADATA,sha256=clg2flS9u3aeaYxXqlwvnemMyDKBHuuMvUgiJj8d4qs,5866
31
+ epstein_files-1.1.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
32
+ epstein_files-1.1.2.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
33
+ epstein_files-1.1.2.dist-info/RECORD,,
@@ -1,33 +0,0 @@
1
- epstein_files/__init__.py,sha256=Roo3XeVQXkgMOQMdq74-dBdkRGeY3sAv7LihZwaGvaY,5538
2
- epstein_files/documents/communication.py,sha256=oqNsSDWe-N0jSmchIHxpihihzIWha-foFqMwKZlxyng,2057
3
- epstein_files/documents/document.py,sha256=s77ZQwwYmm-PDc2rZqg01FY4hpBh-XF5OHcgTVZZBes,17395
4
- epstein_files/documents/email.py,sha256=rGnJjRzLnq70jZpWU3oslNW-FhztlcszS3v7mwEcmzY,41222
5
- epstein_files/documents/emails/email_header.py,sha256=wkPfSLbmzkAeQwvhf0bAeFDLPbQT-EeG0v8vNNLYktM,7502
6
- epstein_files/documents/imessage/text_message.py,sha256=icIiKRRuZapkV9r_PID_7hEfy7YvPrIm9Emc4QiYxbw,2806
7
- epstein_files/documents/json_file.py,sha256=WcZW5NNqA67rHTdopbOGtup00muNaLlvrNgKb-K4zO8,1504
8
- epstein_files/documents/messenger_log.py,sha256=zRVVe82cNmNmg50kqScH38yGGoSx8NjCWfLwFGUb0rs,6501
9
- epstein_files/documents/other_file.py,sha256=CCwOYsipYTWZj8pSTl0kgUy_LRu7Z5ZuWygQEYhilNA,9778
10
- epstein_files/epstein_files.py,sha256=LsvD9O1YNp9xFIVOrswOfnbfafxgbS9ve9cqSfQENy8,17881
11
- epstein_files/util/constant/common_words.py,sha256=aR0UjoWmxyR49XS-DtHECQ1CiA_bK8hNP6CQ1TS9yZA,3696
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
- epstein_files/util/constant/strings.py,sha256=rOKmYrGIZFPQbhCgd8z7mUF0CbQQBO4ij8kXvGTDkoE,1857
16
- epstein_files/util/constant/urls.py,sha256=-cylNL7xJi18a6fawVTF8MO3oEdjMvo9Hg1MwC-7ydI,5098
17
- epstein_files/util/constants.py,sha256=4WABkEBshduSV4cZkxv0MwgJci9g6MxjhtJfPjersEo,112495
18
- epstein_files/util/data.py,sha256=JccGFZGiCGm7XtwpQTocIjGYOr6hTUpEPwHhjyW9Xnc,3164
19
- epstein_files/util/doc_cfg.py,sha256=aBIm0hyxf-aeMsb8ZUNiQFVsPFimjVUIkrVdDrg1iQU,9105
20
- epstein_files/util/env.py,sha256=AWZYDHQ6JRHobY1o7BHRbVObYpBejhsUN4TrOyZbzgQ,5783
21
- epstein_files/util/file_helper.py,sha256=uJzNHihuAi2-XMNbUdjdovgppcIt-fVIIrXHXVShA6Q,2904
22
- epstein_files/util/highlighted_group.py,sha256=4r-asWz53zVHKckONxTOluFCRqbX92nAID1gdXqf08g,37047
23
- epstein_files/util/logging.py,sha256=fuREq06xUUI3DfCV2JE-8QM-sQKxpLDj0_AYFO6qR1M,1983
24
- epstein_files/util/output.py,sha256=WK37ZbTvhPHczH8GNly2Pjw9IwAsiF_yn43u7Q2jf7k,10051
25
- epstein_files/util/rich.py,sha256=oic1wcqipTSj9G1YYP8HTzsJvGeIgrK8vN95uGOJ8sU,15715
26
- epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
27
- epstein_files/util/timer.py,sha256=8hxW4Y1JcTUfnBrHh7sL2pM9xu1sL4HFQM4CmmzTarU,837
28
- epstein_files/util/word_count.py,sha256=o_-HnfzHdPDPR8oA_dv6fjy1dbsHee8p_aoe62PEQHw,9213
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,,