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.
- epstein_files/__init__.py +15 -7
- epstein_files/documents/communication.py +3 -3
- epstein_files/documents/document.py +10 -3
- epstein_files/documents/email.py +105 -107
- epstein_files/documents/emails/email_header.py +4 -2
- epstein_files/documents/imessage/text_message.py +8 -12
- epstein_files/documents/messenger_log.py +8 -8
- epstein_files/epstein_files.py +123 -119
- epstein_files/person.py +350 -0
- epstein_files/util/constant/names.py +66 -50
- epstein_files/util/constant/output_files.py +1 -0
- epstein_files/util/constant/strings.py +3 -1
- epstein_files/util/constant/urls.py +14 -2
- epstein_files/util/constants.py +134 -26
- epstein_files/util/data.py +1 -12
- epstein_files/util/doc_cfg.py +30 -14
- epstein_files/util/env.py +3 -1
- epstein_files/util/file_helper.py +4 -1
- epstein_files/util/highlighted_group.py +228 -166
- epstein_files/util/output.py +108 -165
- epstein_files/util/rich.py +23 -45
- epstein_files/util/word_count.py +2 -3
- {epstein_files-1.1.3.dist-info → epstein_files-1.2.0.dist-info}/METADATA +2 -1
- epstein_files-1.2.0.dist-info/RECORD +34 -0
- epstein_files-1.1.3.dist-info/RECORD +0 -33
- {epstein_files-1.1.3.dist-info → epstein_files-1.2.0.dist-info}/LICENSE +0 -0
- {epstein_files-1.1.3.dist-info → epstein_files-1.2.0.dist-info}/WHEEL +0 -0
- {epstein_files-1.1.3.dist-info → epstein_files-1.2.0.dist-info}/entry_points.txt +0 -0
epstein_files/util/output.py
CHANGED
|
@@ -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
|
|
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
|
|
15
|
-
from epstein_files.util.data import dict_sets_to_lists,
|
|
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
|
-
|
|
50
|
-
'
|
|
51
|
-
'
|
|
52
|
-
|
|
53
|
-
|
|
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 = [
|
|
60
|
-
|
|
61
|
-
table.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
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
|
-
|
|
109
|
+
people_to_print = epstein_files.person_objs(args.names)
|
|
94
110
|
else:
|
|
95
111
|
if args.all_emails:
|
|
96
|
-
|
|
112
|
+
people_to_print = all_emailers
|
|
97
113
|
else:
|
|
98
|
-
|
|
114
|
+
people_to_print = epstein_files.person_objs(DEFAULT_EMAILERS)
|
|
99
115
|
|
|
100
116
|
print_other_page_link(epstein_files)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
116
|
-
|
|
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,
|
|
148
|
+
_verify_all_emails_were_printed(epstein_files, printed_emails)
|
|
120
149
|
|
|
121
|
-
|
|
122
|
-
|
|
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
|
|
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("
|
|
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
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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:
|
epstein_files/util/rich.py
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
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'
|
|
263
|
-
|
|
264
|
-
|
|
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",
|
|
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)
|
epstein_files/util/word_count.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|