epstein-files 1.1.3__py3-none-any.whl → 1.2.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.
@@ -1,31 +1,37 @@
1
1
  import json
2
+ from os import unlink
3
+ from typing import cast
2
4
 
3
5
  from rich.padding import Padding
4
6
 
5
7
  from epstein_files.documents.document import Document
6
- from epstein_files.documents.email import KRASSNER_RECIPIENTS, Email
8
+ from epstein_files.documents.email import Email
7
9
  from epstein_files.documents.messenger_log import MessengerLog
8
10
  from epstein_files.documents.other_file import FIRST_FEW_LINES, OtherFile
9
11
  from epstein_files.epstein_files import EpsteinFiles, count_by_month
12
+ from epstein_files.person import Person
10
13
  from epstein_files.util.constant import output_files
11
14
  from epstein_files.util.constant.html import *
12
15
  from epstein_files.util.constant.names import *
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, TIMESTAMP_STYLE
15
- from epstein_files.util.data import dict_sets_to_lists, sort_dict
16
+ from epstein_files.util.constant.output_files import EMAILERS_TABLE_PNG_PATH, JSON_FILES_JSON_PATH, JSON_METADATA_PATH
17
+ from epstein_files.util.constant.strings import AUTHOR, TIMESTAMP_STYLE
18
+ from epstein_files.util.data import dict_sets_to_lists, uniquify
16
19
  from epstein_files.util.env import args
17
20
  from epstein_files.util.file_helper import log_file_write
18
- from epstein_files.util.highlighted_group import QUESTION_MARKS_TXT
19
21
  from epstein_files.util.logging import logger
20
22
  from epstein_files.util.rich import *
21
23
 
24
+ DEVICE_SIGNATURE_SUBTITLE = f"Email [italic]Sent from \\[DEVICE][/italic] Signature Breakdown"
25
+ DEVICE_SIGNATURE = 'Device Signature'
26
+ DEVICE_SIGNATURE_PADDING = (1, 0)
27
+ OTHER_INTERESTING_EMAILS_SUBTITLE = 'Other Interesting Emails\n(these emails have been flagged as being of particular interest)'
22
28
  PRINT_COLOR_KEY_EVERY_N_EMAILS = 150
23
29
 
24
30
  # Order matters. Default names to print emails for.
25
31
  DEFAULT_EMAILERS = [
26
32
  JEREMY_RUBIN,
27
- JOI_ITO,
28
33
  JABOR_Y,
34
+ JOI_ITO,
29
35
  STEVEN_SINOFSKY,
30
36
  AL_SECKEL,
31
37
  DANIEL_SIAD,
@@ -44,84 +50,108 @@ DEFAULT_EMAILERS = [
44
50
  JENNIFER_JACQUET,
45
51
  ZUBAIR_KHAN,
46
52
  None,
53
+ JEFFREY_EPSTEIN,
47
54
  ]
48
55
 
49
- INVALID_FOR_EPSTEIN_WEB = JUNK_EMAILERS + KRASSNER_RECIPIENTS + [
50
- 'ACT for America',
51
- 'BS Stern',
52
- INTELLIGENCE_SQUARED,
53
- UNKNOWN,
56
+ INTERESTING_EMAIL_IDS = [
57
+ '032229', # Michael Wolff on strategy
58
+ '028784', # seminars: Money / Power
59
+ '029342', # Hakeem Jeffries
60
+ '023454', # Email invitation sent to tech CEOs + Epstein
61
+ '030630', # 'What happens with zubair's project?'
62
+ '033178', # 'How is it going with Zubair?'
63
+ '022396', # Ukraine friend
64
+ '026505', # I know how dirty trump is
65
+ '029679', # Trump's driver was the bag man
66
+ '030781', '026258', '026260', # Bannon cripto coin issues
67
+ '023627', # Michael Wolff article w/Brock
68
+ '032359', # Jabor e-currency
69
+ #'023208', # Extremely long Leon Black email chain
54
70
  ]
55
71
 
56
72
 
57
73
  def print_email_timeline(epstein_files: EpsteinFiles) -> None:
58
74
  """Print a table of all emails in chronological order."""
59
- emails = [email for email in epstein_files.non_duplicate_emails() if not email.is_junk_mail()]
60
- table = build_table(f'All {len(emails):,} Non-Junk Emails in Chronological Order', highlight=True)
61
- table.add_column('ID', style=TIMESTAMP_DIM)
62
- table.add_column('Sent At', style='dim')
63
- table.add_column('Author', max_width=20)
64
- table.add_column('Recipients', max_width=22)
65
- table.add_column('Length', justify='right', style='wheat4')
66
- table.add_column('Subject')
67
-
68
- for email in Document.sort_by_timestamp(emails):
69
- if email.is_junk_mail():
70
- continue
71
-
72
- table.add_row(
73
- email.epstein_media_link(link_txt=email.source_file_id()),
74
- email.timestamp_without_seconds(),
75
- email.author_txt(),
76
- email.recipients_txt(max_full_names=1),
77
- f"{email.length()}",
78
- email.subject(),
79
- )
80
-
81
- console.line(2)
82
- console.print(table)
75
+ emails = Document.sort_by_timestamp([e for e in epstein_files.non_duplicate_emails() if not e.is_mailing_list()])
76
+ title = f'Table of All {len(emails):,} Non-Junk Emails in Chronological Order (actual emails below)'
77
+ table = Email.build_emails_table(emails, title=title, show_length=True)
78
+ console.print(Padding(table, (2, 0)))
79
+ print_subtitle_panel('The Chronologically Ordered Emails')
80
+ console.line()
81
+
82
+ for email in emails:
83
+ console.print(email)
84
+
85
+
86
+ def print_emailers_info_png(epstein_files: EpsteinFiles) -> None:
87
+ print_color_key()
88
+ console.line()
89
+ all_emailers = sorted(epstein_files.emailers(), key=lambda person: person.sort_key())
90
+ console.print(Person.emailer_info_table(all_emailers))
91
+ svg_path = f"{EMAILERS_TABLE_PNG_PATH}.svg"
92
+ console.save_svg(svg_path, theme=HTML_TERMINAL_THEME, title="Epstein Emailers")
93
+ log_file_write(svg_path)
94
+ import cairosvg
95
+ cairosvg.svg2png(url=svg_path, write_to=str(EMAILERS_TABLE_PNG_PATH))
96
+ log_file_write(EMAILERS_TABLE_PNG_PATH)
97
+ unlink(svg_path)
83
98
 
84
99
 
85
100
  def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
86
101
  """Returns emails that were printed (may contain dupes if printed for both author and recipient)."""
87
102
  print_section_header(('Selections from ' if not args.all_emails else '') + 'His Emails')
88
- emailers_to_print: list[str | None]
89
- already_printed_emails: list[Email] = []
103
+ all_emailers = sorted(epstein_files.emailers(), key=lambda person: person.earliest_email_at())
90
104
  num_emails_printed_since_last_color_key = 0
105
+ printed_emails: list[Email] = []
106
+ people_to_print: list[Person]
91
107
 
92
108
  if args.names:
93
- emailers_to_print = args.names
109
+ people_to_print = epstein_files.person_objs(args.names)
94
110
  else:
95
111
  if args.all_emails:
96
- emailers_to_print = sorted(epstein_files.all_emailers(), key=lambda e: epstein_files.earliest_email_at(e))
112
+ people_to_print = all_emailers
97
113
  else:
98
- emailers_to_print = DEFAULT_EMAILERS
114
+ people_to_print = epstein_files.person_objs(DEFAULT_EMAILERS)
99
115
 
100
116
  print_other_page_link(epstein_files)
101
- console.line(2)
102
- console.print(_table_of_selected_emailers(emailers_to_print, epstein_files))
103
- console.print(Padding(_all_emailers_table(epstein_files), (2, 0)))
117
+ print_centered(Padding(Person.emailer_info_table(all_emailers, people_to_print), (2, 0, 1, 0)))
118
+
119
+ for person in people_to_print:
120
+ if person.name in epstein_files.uninteresting_emailers() and not args.names:
121
+ continue
104
122
 
105
- for author in emailers_to_print:
106
- author_emails = epstein_files.print_emails_for(author)
107
- already_printed_emails.extend(author_emails)
108
- num_emails_printed_since_last_color_key += len(author_emails)
123
+ printed_person_emails = person.print_emails()
124
+ printed_emails.extend(printed_person_emails)
125
+ num_emails_printed_since_last_color_key += len(printed_person_emails)
109
126
 
110
127
  # Print color key every once in a while
111
128
  if num_emails_printed_since_last_color_key > PRINT_COLOR_KEY_EVERY_N_EMAILS:
112
129
  print_color_key()
113
130
  num_emails_printed_since_last_color_key = 0
114
131
 
115
- if not args.names:
116
- epstein_files.print_email_device_info()
132
+ if args.names:
133
+ return printed_emails
134
+
135
+ # Print other interesting emails
136
+ printed_email_ids = [email.file_id for email in printed_emails]
137
+ extra_emails = [e for e in epstein_files.for_ids(INTERESTING_EMAIL_IDS) if e.file_id not in printed_email_ids]
138
+
139
+ if len(extra_emails) > 0:
140
+ print_subtitle_panel(OTHER_INTERESTING_EMAILS_SUBTITLE)
141
+ console.line()
142
+
143
+ for other_email in extra_emails:
144
+ console.print(other_email)
145
+ printed_emails.append(cast(Email, other_email))
117
146
 
118
147
  if args.all_emails:
119
- _verify_all_emails_were_printed(epstein_files, already_printed_emails)
148
+ _verify_all_emails_were_printed(epstein_files, printed_emails)
120
149
 
121
- fwded_articles = [e for e in already_printed_emails if e.config and e.is_fwded_article()]
122
- log_msg = f"Rewrote {len(Email.rewritten_header_ids)} of {len(already_printed_emails)} email headers"
150
+ _print_email_device_info(epstein_files)
151
+ fwded_articles = [e for e in printed_emails if e.config and e.is_fwded_article()]
152
+ log_msg = f"Rewrote {len(Email.rewritten_header_ids)} of {len(printed_emails)} email headers"
123
153
  logger.warning(f"{log_msg}, {len(fwded_articles)} of the emails were forwarded articles.")
124
- return already_printed_emails
154
+ return printed_emails
125
155
 
126
156
 
127
157
  def print_json_files(epstein_files: EpsteinFiles):
@@ -154,12 +184,12 @@ def print_json_stats(epstein_files: EpsteinFiles) -> None:
154
184
  console.line(5)
155
185
  console.print(Panel('JSON Stats Dump', expand=True, style='reverse bold'), '\n')
156
186
  print_json(f"MessengerLog Sender Counts", MessengerLog.count_authors(epstein_files.imessage_logs), skip_falsey=True)
157
- print_json(f"Email Author Counts", epstein_files.email_author_counts, skip_falsey=True)
158
- print_json(f"Email Recipient Counts", epstein_files.email_recipient_counts, skip_falsey=True)
187
+ print_json(f"Email Author Counts", epstein_files.email_author_counts(), skip_falsey=True)
188
+ print_json(f"Email Recipient Counts", epstein_files.email_recipient_counts(), skip_falsey=True)
159
189
  print_json("Email signature_substitution_countss", epstein_files.email_signature_substitution_counts(), skip_falsey=True)
160
- print_json("email_author_device_signatures", dict_sets_to_lists(epstein_files.email_authors_to_device_signatures))
161
- print_json("email_sent_from_devices", dict_sets_to_lists(epstein_files.email_device_signatures_to_authors))
162
- print_json("email_unknown_recipient_file_ids", epstein_files.email_unknown_recipient_file_ids())
190
+ print_json("email_author_device_signatures", dict_sets_to_lists(epstein_files.email_authors_to_device_signatures()))
191
+ print_json("email_sent_from_devices", dict_sets_to_lists(epstein_files.email_device_signatures_to_authors()))
192
+ print_json("unknown_recipient_ids", epstein_files.unknown_recipient_ids())
163
193
  print_json("count_by_month", count_by_month(epstein_files.all_documents()))
164
194
 
165
195
 
@@ -215,113 +245,26 @@ def write_urls() -> None:
215
245
  logger.warning(f"Wrote {len(url_vars)} URL variables to '{URLS_ENV}'\n")
216
246
 
217
247
 
218
- def _all_emailers_table(epstein_files: EpsteinFiles) -> Table:
219
- attributed_emails = [e for e in epstein_files.non_duplicate_emails() if e.author]
220
- footer = f"(identified {len(epstein_files.email_author_counts)} authors of {len(attributed_emails):,}"
221
- footer = f"{footer} out of {len(epstein_files.non_duplicate_emails()):,} emails)"
222
- counts_table = build_table("All of the Email Counterparties Who Appear in the Files", caption=footer)
223
-
224
- add_cols_to_table(counts_table, [
225
- 'Name',
226
- {'name': 'Count', 'justify': 'right', 'style': 'bold bright_white'},
227
- {'name': 'Sent', 'justify': 'right', 'style': 'gray74'},
228
- {'name': 'Recv', 'justify': 'right', 'style': 'gray74'},
229
- {'name': 'First', 'style': TIMESTAMP_STYLE},
230
- {'name': 'Last', 'style': LAST_TIMESTAMP_STYLE},
231
- {'name': 'Days', 'justify': 'right', 'style': 'dim'},
232
- JMAIL,
233
- EPSTEIN_MEDIA,
234
- EPSTEIN_WEB,
235
- 'Twitter',
236
- ])
237
-
238
- emailer_counts = {
239
- emailer: epstein_files.email_author_counts[emailer] + epstein_files.email_recipient_counts[emailer]
240
- for emailer in epstein_files.all_emailers(True)
241
- }
242
-
243
- for name, count in sort_dict(emailer_counts):
244
- style = get_style_for_name(name, default_style=DEFAULT_NAME_STYLE)
245
- emails = epstein_files.emails_for(name)
246
-
247
- counts_table.add_row(
248
- Text.from_markup(link_markup(epsteinify_name_url(name or UNKNOWN), name or UNKNOWN, style)),
249
- f"{count:,}",
250
- str(epstein_files.email_author_counts[name]),
251
- str(epstein_files.email_recipient_counts[name]),
252
- emails[0].date_str(),
253
- emails[-1].date_str(),
254
- f"{epstein_files.email_conversation_length_in_days(name)}",
255
- link_text_obj(search_jmail_url(name), JMAIL) if name else '',
256
- link_text_obj(epstein_media_person_url(name), EPSTEIN_MEDIA) if _is_ok_for_epstein_web(name) else '',
257
- link_text_obj(epstein_web_person_url(name), EPSTEIN_WEB) if _is_ok_for_epstein_web(name) else '',
258
- link_text_obj(search_twitter_url(name), 'search X') if name else '',
259
- )
260
-
261
- return counts_table
262
-
263
-
264
- def _is_ok_for_epstein_web(name: str | None) -> bool:
265
- """Return True if it's likely that EpsteinWeb has a page for this name."""
266
- if name is None or ' ' not in name:
267
- return False
268
- elif '@' in name or '/' in name or '??' in name:
269
- return False
270
- elif name in INVALID_FOR_EPSTEIN_WEB:
271
- return False
272
-
273
- return True
274
-
275
-
276
- def _table_of_selected_emailers(_list: list[str | None], epstein_files: EpsteinFiles) -> Table:
277
- """Add the first emailed_at timestamp for each emailer if 'epstein_files' provided."""
278
- header_pfx = '' if args.all_emails else 'Selected '
279
- table = build_table(f'{header_pfx}Email Conversations Grouped by Counterparty Will Appear in this Order')
280
- table.add_column('Start Date')
281
- table.add_column('Name', max_width=25, no_wrap=True)
282
- table.add_column('Category', justify='center', style='dim italic')
283
- table.add_column('Num', justify='right', style='wheat4')
284
- table.add_column('Info', style='white italic')
285
- current_year = 1990
286
- current_year_month = current_year * 12
287
- grey_idx = 0
288
-
289
- for i, name in enumerate(_list):
290
- earliest_email_date = (epstein_files.earliest_email_at(name) or FALLBACK_TIMESTAMP).date()
291
- year_months = (earliest_email_date.year * 12) + earliest_email_date.month
292
-
293
- # Color year rollovers more brightly
294
- if current_year != earliest_email_date.year:
295
- grey_idx = 0
296
- elif current_year_month != year_months:
297
- grey_idx = ((current_year_month - 1) % 12) + 1
298
-
299
- current_year_month = year_months
300
- current_year = earliest_email_date.year
301
- category = get_category_txt_for_name(name)
302
- info = get_info_for_name(name)
303
- style = get_style_for_name(name, default_style='none')
304
-
305
- if category and category.plain == 'paula': # TODO: hacky
306
- category = None
307
- elif category and info:
308
- info = info.removeprefix(f"{category.plain}, ").removeprefix(category.plain)
309
- elif not name:
310
- info = Text('(emails whose author or recipient could not be determined)', style='medium_purple4')
311
- elif name in JUNK_EMAILERS:
312
- category = Text('junk', style='gray30')
313
- elif style == 'none' and '@' not in name and not (category or info):
314
- info = QUESTION_MARKS_TXT
315
-
316
- table.add_row(
317
- Text(str(earliest_email_date), style=f"grey{GREY_NUMBERS[grey_idx]}"),
318
- Text(name or UNKNOWN, style=get_style_for_name(name or UNKNOWN, default_style='dim')),
319
- category,
320
- f"{len(epstein_files.emails_for(name)):,}",
321
- info or '',
322
- )
323
-
324
- return table
248
+ def _print_email_device_info(epstein_files: EpsteinFiles) -> None:
249
+ print_subtitle_panel(DEVICE_SIGNATURE_SUBTITLE)
250
+ console.print(_signature_table(epstein_files.email_device_signatures_to_authors(), (DEVICE_SIGNATURE, AUTHOR), ', '))
251
+ console.print(_signature_table(epstein_files.email_authors_to_device_signatures(), (AUTHOR, DEVICE_SIGNATURE)))
252
+
253
+
254
+ def _signature_table(keyed_sets: dict[str, set[str]], cols: tuple[str, str], join_char: str = '\n') -> Padding:
255
+ """Build table for who signed emails with 'Sent from my iPhone' etc."""
256
+ title = 'Email Signatures Used By Authors' if cols[0] == AUTHOR else 'Authors Seen Using Email Signatures'
257
+ table = build_table(title, header_style="bold reverse", show_lines=True)
258
+
259
+ for i, col in enumerate(cols):
260
+ table.add_column(col.title() + ('s' if i == 1 else ''))
261
+
262
+ new_dict = dict_sets_to_lists(keyed_sets)
263
+
264
+ for k in sorted(new_dict.keys()):
265
+ table.add_row(highlighter(k or UNKNOWN), highlighter(join_char.join(sorted(new_dict[k]))))
266
+
267
+ return Padding(table, DEVICE_SIGNATURE_PADDING)
325
268
 
326
269
 
327
270
  def _verify_all_emails_were_printed(epstein_files: EpsteinFiles, already_printed_emails: list[Email]) -> None:
@@ -1,10 +1,11 @@
1
1
  # Rich reference: https://rich.readthedocs.io/en/latest/reference.html
2
2
  import json
3
+ from copy import deepcopy
3
4
  from os import devnull
4
5
  from pathlib import Path
5
6
 
6
7
  from rich.align import Align
7
- from rich.console import Console, RenderableType
8
+ from rich.console import Console, Group, RenderableType
8
9
  from rich.markup import escape
9
10
  from rich.panel import Panel
10
11
  from rich.padding import Padding
@@ -14,23 +15,23 @@ from rich.theme import Theme
14
15
 
15
16
  from epstein_files.util.constant.html import CONSOLE_HTML_FORMAT, HTML_TERMINAL_THEME, PAGE_TITLE
16
17
  from epstein_files.util.constant.names import UNKNOWN
17
- from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, QUESTION_MARKS, TEXT_MESSAGE
18
+ from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, TEXT_MESSAGE
18
19
  from epstein_files.util.constant.urls import *
19
- from epstein_files.util.constants import FALLBACK_TIMESTAMP, HEADER_ABBREVIATIONS
20
+ from epstein_files.util.constants import HEADER_ABBREVIATIONS
20
21
  from epstein_files.util.data import json_safe
21
22
  from epstein_files.util.env import args
22
23
  from epstein_files.util.file_helper import log_file_write
23
- from epstein_files.util.highlighted_group import (ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter,
24
- get_category_txt_for_name, get_info_for_name, get_style_for_name)
24
+ from epstein_files.util.highlighted_group import ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter
25
25
  from epstein_files.util.logging import logger
26
26
 
27
27
  TITLE_WIDTH = 50
28
- MIN_AUTHOR_PANEL_WIDTH = 80
28
+ SUBTITLE_WIDTH = 110
29
29
  NUM_COLOR_KEY_COLS = 4
30
30
  NA_TXT = Text(NA, style='dim')
31
+ SUBTITLE_PADDING = (2, 0, 1, 0)
31
32
  GREY_NUMBERS = [58, 39, 39, 35, 30, 27, 23, 23, 19, 19, 15, 15, 15]
33
+ VALID_GREYS = [0, 3, 7, 11, 15, 19, 23, 27, 30, 35, 37, 39, 42, 46, 50, 53, 54, 58, 62, 63, 66, 69, 70, 74, 78, 82, 84, 85, 89, 93]
32
34
 
33
- DEFAULT_NAME_STYLE = 'gray46'
34
35
  INFO_STYLE = 'white dim italic'
35
36
  KEY_STYLE = 'honeydew2 bold'
36
37
  LAST_TIMESTAMP_STYLE = 'wheat4'
@@ -89,17 +90,14 @@ def add_cols_to_table(table: Table, col_names: list[str | dict]) -> None:
89
90
 
90
91
  if isinstance(col, dict):
91
92
  col_name = col['name']
92
- kwargs = col
93
+ kwargs = deepcopy(col)
94
+ kwargs['justify'] = kwargs.get('justify', justify)
93
95
  del kwargs['name']
94
-
95
- if 'justify' in col:
96
- justify = col['justify']
97
- del col['justify']
98
96
  else:
99
97
  col_name = col
100
- kwargs = {}
98
+ kwargs = {'justify': justify}
101
99
 
102
- table.add_column(col_name, justify=justify, **kwargs)
100
+ table.add_column(col_name, **kwargs)
103
101
 
104
102
 
105
103
  def build_highlighter(pattern: str) -> EpsteinHighlighter:
@@ -110,7 +108,7 @@ def build_highlighter(pattern: str) -> EpsteinHighlighter:
110
108
  return TempHighlighter()
111
109
 
112
110
 
113
- def build_table(title: str | None, cols: list[str | dict] | None = None, **kwargs) -> Table:
111
+ def build_table(title: str | Text | None, cols: list[str | dict] | None = None, **kwargs) -> Table:
114
112
  table = Table(title=title, **{**DEFAULT_TABLE_KWARGS, **kwargs})
115
113
 
116
114
  if cols:
@@ -151,19 +149,6 @@ def parenthesize(msg: str | Text, style: str = '') -> Text:
151
149
  return Text('(', style=style).append(txt).append(')')
152
150
 
153
151
 
154
- def print_author_panel(msg: str, color: str | None, footer: str | None = None) -> None:
155
- """Print a panel with the name of an emailer and a few tidbits of information about them."""
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")
159
- console.print('\n', Align.center(panel))
160
-
161
- if footer:
162
- console.print(Align.center(f"({footer})"), highlight=False, style=f'{color} italic')
163
-
164
- console.line()
165
-
166
-
167
152
  def print_centered(obj: RenderableType, style: str = '') -> None:
168
153
  console.print(Align.center(obj), style=style)
169
154
 
@@ -188,7 +173,8 @@ def print_color_key() -> None:
188
173
  print_centered(vertically_pad(color_table))
189
174
 
190
175
 
191
- def print_title_page_header(epstein_files: 'EpsteinFiles') -> None:
176
+ def print_title_page_header() -> None:
177
+ """Top half of the title page."""
192
178
  print_page_title(width=TITLE_WIDTH)
193
179
  site_type = EMAIL if (args.all_emails or args.email_timeline) else TEXT_MESSAGE
194
180
  title = f"This is the " + ('chronological ' if args.email_timeline else '') + f"Epstein {site_type.title()}s Page"
@@ -209,6 +195,7 @@ def print_title_page_header(epstein_files: 'EpsteinFiles') -> None:
209
195
 
210
196
 
211
197
  def print_title_page_tables(epstein_files: 'EpsteinFiles') -> None:
198
+ """Bottom half of the title page."""
212
199
  _print_external_links()
213
200
  console.line()
214
201
  _print_abbreviations_table()
@@ -234,19 +221,17 @@ def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
234
221
 
235
222
 
236
223
  def print_other_page_link(epstein_files: 'EpsteinFiles') -> None:
237
- markup_msg = link_markup(other_site_url(), 'the other page', style='light_slate_grey bold')
238
-
239
224
  if other_site_type() == EMAIL:
240
- txt = Text.from_markup(markup_msg).append(f' is uncurated and has all {len(epstein_files.emails):,} emails')
225
+ txt = THE_OTHER_PAGE_TXT + Text(f' is uncurated and has all {len(epstein_files.emails):,} emails')
241
226
  txt.append(f" and {len(epstein_files.other_files)} unclassifiable files")
242
227
  else:
243
- txt = Text.from_markup(markup_msg).append(f' displays a limited collection of emails and')
228
+ txt = THE_OTHER_PAGE_TXT + (f' displays a limited collection of emails and')
244
229
  txt.append(" unclassifiable files of particular interest")
245
230
 
246
231
  print_centered(parenthesize(txt), style=OTHER_PAGE_MSG_STYLE)
247
232
  chrono_emails_markup = link_text_obj(CHRONOLOGICAL_EMAILS_URL, 'a page', style='light_slate_grey bold')
248
233
  chrono_emails_txt = Text(f"there's also ").append(chrono_emails_markup)
249
- chrono_emails_txt.append(' with a table of all the emails in chronological order')
234
+ chrono_emails_txt.append(' with all the emails in chronological order')
250
235
  print_centered(parenthesize(chrono_emails_txt), style=OTHER_PAGE_MSG_STYLE)
251
236
 
252
237
 
@@ -259,16 +244,9 @@ def print_page_title(expand: bool = True, width: int | None = None) -> None:
259
244
  console.line(2)
260
245
 
261
246
 
262
- def print_subtitle_panel(msg: str, style: str = 'black on white', padding: tuple | None = None, centered: bool = False) -> None:
263
- _padding: list[int] = list(padding or [0, 0, 0, 0])
264
- _padding[2] += 1 # Bottom pad
265
- actual_padding: tuple[int, int, int, int] = tuple(_padding)
266
- panel = Panel(Text.from_markup(msg, justify='center'), width=70, style=style)
267
-
268
- if centered:
269
- console.print(Align.center(Padding(panel, actual_padding)))
270
- else:
271
- console.print(Padding(panel, actual_padding))
247
+ def print_subtitle_panel(msg: str, style: str = 'black on white') -> None:
248
+ panel = Panel(Text.from_markup(msg, justify='center'), width=SUBTITLE_WIDTH, style=style)
249
+ print_centered(Padding(panel, SUBTITLE_PADDING))
272
250
 
273
251
 
274
252
  def print_section_header(msg: str, style: str = SECTION_HEADER_STYLE, is_centered: bool = False) -> None:
@@ -318,7 +296,7 @@ def write_html(output_path: Path | None) -> None:
318
296
  def _print_abbreviations_table() -> None:
319
297
  table = build_table(title="Abbreviations Used Frequently In These Conversations", show_header=False)
320
298
  table.add_column("Abbreviation", justify="center", style='bold')
321
- table.add_column("Translation", style="white", justify="center")
299
+ table.add_column("Translation", justify="center", min_width=62, style="white")
322
300
 
323
301
  for k, v in HEADER_ABBREVIATIONS.items():
324
302
  table.add_row(highlighter(k), v)
@@ -197,7 +197,7 @@ def write_word_counts_html() -> None:
197
197
  email_subjects: set[str] = set()
198
198
  word_count = WordCount()
199
199
  # Remove dupes, junk mail, and fwded articles from emails
200
- emails = [e for e in epstein_files.non_duplicate_emails() if not (e.is_junk_mail() or e.is_fwded_article())]
200
+ emails = [e for e in epstein_files.non_duplicate_emails() if not (e.is_mailing_list() or e.is_fwded_article())]
201
201
 
202
202
  for email in emails:
203
203
  if args.names and email.author not in args.names:
@@ -237,8 +237,7 @@ def write_word_counts_html() -> None:
237
237
  print_color_key()
238
238
  console.line()
239
239
  console.print(word_count)
240
- console.line(2)
241
- print_subtitle_panel(f"{len(COMMON_WORDS_LIST):,} Excluded Words", centered=True)
240
+ print_subtitle_panel(f"{len(COMMON_WORDS_LIST):,} Excluded Words")
242
241
  console.print(', '.join(COMMON_WORDS_LIST), highlight=False)
243
242
  write_html(WORD_COUNT_HTML_PATH)
244
243
  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.3
3
+ Version: 1.2.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
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Requires-Dist: cairosvg (>=2.8.2,<3.0.0)
20
21
  Requires-Dist: datefinder (>=0.7.3,<0.8.0)
21
22
  Requires-Dist: inflection (>=0.5.1,<0.6.0)
22
23
  Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
@@ -0,0 +1,34 @@
1
+ epstein_files/__init__.py,sha256=HfNEIZ8I8h44GdWTaS0UTA2NjIGZYoqcdTFGV9OnACM,5403
2
+ epstein_files/documents/communication.py,sha256=NzcZ3vQBjVAovasnxpUyII4weycMaJ2T3fc_8d4eg-U,1875
3
+ epstein_files/documents/document.py,sha256=eDPN06KztjWcb6LCmNxqud0fcckfr6ZySHO1TxbjaCY,17643
4
+ epstein_files/documents/email.py,sha256=7zLkvyHdv2fSTKGrT6eW2b6VqaTXpBWPzCKXqOQwl6s,42083
5
+ epstein_files/documents/emails/email_header.py,sha256=P2dZno30W49oqf9MixiAZbtxX1ooe-yJ6VVa_R24ySw,7588
6
+ epstein_files/documents/imessage/text_message.py,sha256=Tx81FVz8LwQwC_ZhMa5XI-qlF2jPNxbAnC2KN1d0fdk,3353
7
+ epstein_files/documents/json_file.py,sha256=WcZW5NNqA67rHTdopbOGtup00muNaLlvrNgKb-K4zO8,1504
8
+ epstein_files/documents/messenger_log.py,sha256=1bv62WoQMKR3gYDrK9W3Xm7cqLbKrkRBV7NTFL2cexE,7349
9
+ epstein_files/documents/other_file.py,sha256=TeEzsfGN_mTFZPhfyt9ihxK9oTCYwI8sRLplTsgpOMY,9893
10
+ epstein_files/epstein_files.py,sha256=7xEgs9rKQen6mNPCk2drWc7VsXg9OylDnmzFk8U4UEM,14500
11
+ epstein_files/person.py,sha256=ZzU2WNEdUXJpwA8M3y99AhfK1oQVlnOy-SNP2b8JkjI,14700
12
+ epstein_files/util/constant/common_words.py,sha256=C1JERPnOGHV2UMC71aEue1i9QTQog-RfT3IzdcYQOYQ,3702
13
+ epstein_files/util/constant/html.py,sha256=MFooFV8KfFBCm9hL1u6A3hi_u37i7lL6UKAYoKQj3PI,1505
14
+ epstein_files/util/constant/names.py,sha256=iaF4vJnbqatC1evCBwFagqCQtQr15OUJC_jYbHgoEfs,11058
15
+ epstein_files/util/constant/output_files.py,sha256=gUZJ4mNoeJy3qTYWr_jhSmQI-_uV_jdLR0YCiaQd_Qg,1982
16
+ epstein_files/util/constant/strings.py,sha256=YgrUSf3fANRw2jr1r8HrmbZpzET7KY7gn-MrrZ4-bQI,2017
17
+ epstein_files/util/constant/urls.py,sha256=6cJVEGFWwQ5CCq5oy-Ukj2-xZvbclMWySVxykaFW-W4,5599
18
+ epstein_files/util/constants.py,sha256=HJK7c9T2y6CxQS1CtB8qObCL6hQ6M92xlH5deJZbc8M,121602
19
+ epstein_files/util/data.py,sha256=CXvQBWZuAnkxOuOv2KtwIZe7jtHtfqAOd6ELIhNeJzI,2983
20
+ epstein_files/util/doc_cfg.py,sha256=RjKRX6BGTJf0J4vAWdEH6kMtfG75Psu3ntNx4MYnMzY,9509
21
+ epstein_files/util/env.py,sha256=6DvDCDdG_2AwXR2sOO5kL3GHtZFQxHs8HT87VjVt9NE,6718
22
+ epstein_files/util/file_helper.py,sha256=MpG1hI7DGs05fV9KSVb_ltc98DC8tv1E_TTo5X_E7Js,3010
23
+ epstein_files/util/highlighted_group.py,sha256=c7p7_P-DfbSqrxIh8Es4A3aFxQFrsv9WBodzqMrVcAQ,55708
24
+ epstein_files/util/logging.py,sha256=F45YqEKAiIb0rDZnOB7XuaY-dOkOKrsfSzO1VVqY508,2097
25
+ epstein_files/util/output.py,sha256=8Hqt-CV_02-GyETrcqm4AM6dWO0OTV9nddOQNJ7eW8k,11836
26
+ epstein_files/util/rich.py,sha256=J4b6TLaJ3HQFQdo5Iib60lRwrjzlHRW2zWEaobkrSEc,13698
27
+ epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
28
+ epstein_files/util/timer.py,sha256=QqqXAQofKPWkDngNwG0mOqRn7nHcAR-BGQjqAwZfXoE,840
29
+ epstein_files/util/word_count.py,sha256=rs-bsSMnGG1BnYUmxYuBLZ8X0-3-jJdaB03v1jOIq4g,9202
30
+ epstein_files-1.2.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
31
+ epstein_files-1.2.0.dist-info/METADATA,sha256=ttXba8M6jt-gbb94ouqD95zOMdTosW8HSUfFJiDLFlI,6032
32
+ epstein_files-1.2.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
33
+ epstein_files-1.2.0.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
34
+ epstein_files-1.2.0.dist-info/RECORD,,
@@ -1,33 +0,0 @@
1
- epstein_files/__init__.py,sha256=L2NKPyyC8RQ1xYYizheGL5Zbq9DlB6PeMDrYQyvQrEY,5194
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=VBD5wC2n5C8mKRq6Syu8MDAzoNJL5JO8PZgE1faOG-w,43166
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=oWfJxe_NJ6LgCfTgNnDsni5Y45bF0dVm5KBRqElIhBg,15198
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=Dkytzixp4a-msROnOH1KROEUM7--BO9-4PAmKBve5B8,10941
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=enwg1muOKBaQQJeX7Js9P9E96u_EEyDutpemkzHaGAM,112746
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=5ZIwBUlIg37HBq_Z_DtkhXNDxPtnxOtJ_HD7ek3IoQI,6555
21
- epstein_files/util/file_helper.py,sha256=PGPqXmt4Oz4bE45ybvaCZfI0w_PGKirTsrv7xw86gmY,2903
22
- epstein_files/util/highlighted_group.py,sha256=-xeakZF1fkW3e8sXhG_6cd5l6sIxcIfovWVFGKIodjA,52935
23
- epstein_files/util/logging.py,sha256=F45YqEKAiIb0rDZnOB7XuaY-dOkOKrsfSzO1VVqY508,2097
24
- epstein_files/util/output.py,sha256=GYgeGidzzVzVGWgTMcH7yjm1SBWxD9KyxziskBG5lrI,14103
25
- epstein_files/util/rich.py,sha256=jx6XIzGkHFXdCFFSGjtcnd8TsedzdR--nyFExKF3XuM,14611
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.3.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
30
- epstein_files-1.1.3.dist-info/METADATA,sha256=8SEtIRmGd8-QztDalAuH58ARvgPsrPn8A0_5OqV6qXw,5991
31
- epstein_files-1.1.3.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
32
- epstein_files-1.1.3.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
33
- epstein_files-1.1.3.dist-info/RECORD,,