epstein-files 1.1.5__py3-none-any.whl → 1.2.1__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 +12 -21
- epstein_files/documents/communication.py +0 -3
- epstein_files/documents/document.py +68 -21
- epstein_files/documents/email.py +54 -70
- epstein_files/documents/emails/email_header.py +14 -4
- epstein_files/documents/imessage/text_message.py +5 -4
- epstein_files/documents/messenger_log.py +7 -7
- epstein_files/documents/other_file.py +16 -34
- epstein_files/epstein_files.py +133 -141
- epstein_files/person.py +324 -0
- epstein_files/util/constant/names.py +46 -15
- epstein_files/util/constant/output_files.py +1 -0
- epstein_files/util/constant/strings.py +3 -3
- epstein_files/util/constant/urls.py +15 -2
- epstein_files/util/constants.py +75 -21
- epstein_files/util/data.py +1 -20
- epstein_files/util/doc_cfg.py +27 -17
- epstein_files/util/env.py +5 -3
- epstein_files/util/highlighted_group.py +248 -203
- epstein_files/util/logging.py +1 -1
- epstein_files/util/output.py +113 -157
- epstein_files/util/rich.py +20 -35
- epstein_files/util/timer.py +14 -0
- epstein_files/util/word_count.py +1 -1
- {epstein_files-1.1.5.dist-info → epstein_files-1.2.1.dist-info}/METADATA +6 -2
- epstein_files-1.2.1.dist-info/RECORD +34 -0
- epstein_files-1.1.5.dist-info/RECORD +0 -33
- {epstein_files-1.1.5.dist-info → epstein_files-1.2.1.dist-info}/LICENSE +0 -0
- {epstein_files-1.1.5.dist-info → epstein_files-1.2.1.dist-info}/WHEEL +0 -0
- {epstein_files-1.1.5.dist-info → epstein_files-1.2.1.dist-info}/entry_points.txt +0 -0
epstein_files/util/logging.py
CHANGED
|
@@ -33,7 +33,7 @@ LOG_LEVEL_ENV_VAR = 'LOG_LEVEL'
|
|
|
33
33
|
# Augment the standard log highlighter with 'epstein_filename' matcher
|
|
34
34
|
class LogHighlighter(ReprHighlighter):
|
|
35
35
|
highlights = ReprHighlighter.highlights + [
|
|
36
|
-
*[fr"(?P<{doc_type}>{doc_type}(Cfg)?)" for doc_type in DOC_TYPE_STYLES.keys()],
|
|
36
|
+
*[fr"(?P<{doc_type}>{doc_type}(Cfg|s)?)" for doc_type in DOC_TYPE_STYLES.keys()],
|
|
37
37
|
"(?P<epstein_filename>" + FILE_NAME_REGEX.pattern + ')',
|
|
38
38
|
]
|
|
39
39
|
|
epstein_files/util/output.py
CHANGED
|
@@ -1,28 +1,31 @@
|
|
|
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.
|
|
19
|
-
get_info_for_name, get_style_for_name, styled_name)
|
|
20
|
-
from epstein_files.util.logging import logger
|
|
21
|
+
from epstein_files.util.logging import logger, exit_with_error
|
|
21
22
|
from epstein_files.util.rich import *
|
|
22
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)
|
|
23
27
|
OTHER_INTERESTING_EMAILS_SUBTITLE = 'Other Interesting Emails\n(these emails have been flagged as being of particular interest)'
|
|
24
28
|
PRINT_COLOR_KEY_EVERY_N_EMAILS = 150
|
|
25
|
-
ALT_INFO_STYLE = 'medium_purple4'
|
|
26
29
|
|
|
27
30
|
# Order matters. Default names to print emails for.
|
|
28
31
|
DEFAULT_EMAILERS = [
|
|
@@ -58,20 +61,23 @@ INTERESTING_EMAIL_IDS = [
|
|
|
58
61
|
'030630', # 'What happens with zubair's project?'
|
|
59
62
|
'033178', # 'How is it going with Zubair?'
|
|
60
63
|
'022396', # Ukraine friend
|
|
61
|
-
|
|
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
|
|
62
70
|
]
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
'
|
|
66
|
-
'
|
|
67
|
-
INTELLIGENCE_SQUARED,
|
|
68
|
-
UNKNOWN,
|
|
72
|
+
INTERESTING_TEXT_IDS = [
|
|
73
|
+
'027275', # "Crypto- Kerry- Qatar -sessions"
|
|
74
|
+
'027165', # melaniee walker crypto health
|
|
69
75
|
]
|
|
70
76
|
|
|
71
77
|
|
|
72
78
|
def print_email_timeline(epstein_files: EpsteinFiles) -> None:
|
|
73
79
|
"""Print a table of all emails in chronological order."""
|
|
74
|
-
emails = Document.sort_by_timestamp([e for e in epstein_files.non_duplicate_emails() if not e.
|
|
80
|
+
emails = Document.sort_by_timestamp([e for e in epstein_files.non_duplicate_emails() if not e.is_mailing_list()])
|
|
75
81
|
title = f'Table of All {len(emails):,} Non-Junk Emails in Chronological Order (actual emails below)'
|
|
76
82
|
table = Email.build_emails_table(emails, title=title, show_length=True)
|
|
77
83
|
console.print(Padding(table, (2, 0)))
|
|
@@ -82,30 +88,55 @@ def print_email_timeline(epstein_files: EpsteinFiles) -> None:
|
|
|
82
88
|
console.print(email)
|
|
83
89
|
|
|
84
90
|
|
|
91
|
+
def print_emailers_info(epstein_files: EpsteinFiles) -> None:
|
|
92
|
+
"""Print tbe summary table of everyone in the files to an image."""
|
|
93
|
+
print_color_key()
|
|
94
|
+
console.line()
|
|
95
|
+
all_emailers = sorted(epstein_files.emailers(), key=lambda person: person.sort_key())
|
|
96
|
+
console.print(Person.emailer_info_table(all_emailers, show_epstein_total=True))
|
|
97
|
+
|
|
98
|
+
if not args.build:
|
|
99
|
+
logger.warning(f"Not writing .png file because --build is not set")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
svg_path = f"{EMAILERS_TABLE_PNG_PATH}.svg"
|
|
103
|
+
console.save_svg(svg_path, theme=HTML_TERMINAL_THEME, title="Epstein Emailers")
|
|
104
|
+
log_file_write(svg_path)
|
|
105
|
+
import cairosvg
|
|
106
|
+
cairosvg.svg2png(url=svg_path, write_to=str(EMAILERS_TABLE_PNG_PATH))
|
|
107
|
+
log_file_write(EMAILERS_TABLE_PNG_PATH)
|
|
108
|
+
unlink(svg_path)
|
|
109
|
+
|
|
110
|
+
|
|
85
111
|
def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
|
|
86
112
|
"""Returns emails that were printed (may contain dupes if printed for both author and recipient)."""
|
|
87
113
|
print_section_header(('Selections from ' if not args.all_emails else '') + 'His Emails')
|
|
88
|
-
|
|
89
|
-
already_printed_emails: list[Email] = []
|
|
114
|
+
all_emailers = sorted(epstein_files.emailers(), key=lambda person: person.earliest_email_at())
|
|
90
115
|
num_emails_printed_since_last_color_key = 0
|
|
116
|
+
printed_emails: list[Email] = []
|
|
117
|
+
people_to_print: list[Person]
|
|
91
118
|
|
|
92
119
|
if args.names:
|
|
93
|
-
|
|
120
|
+
try:
|
|
121
|
+
people_to_print = epstein_files.person_objs(args.names)
|
|
122
|
+
except Exception as e:
|
|
123
|
+
exit_with_error(str(e))
|
|
94
124
|
else:
|
|
95
125
|
if args.all_emails:
|
|
96
|
-
|
|
126
|
+
people_to_print = all_emailers
|
|
97
127
|
else:
|
|
98
|
-
|
|
128
|
+
people_to_print = epstein_files.person_objs(DEFAULT_EMAILERS)
|
|
99
129
|
|
|
100
130
|
print_other_page_link(epstein_files)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
131
|
+
print_centered(Padding(Person.emailer_info_table(all_emailers, people_to_print), (2, 0, 1, 0)))
|
|
132
|
+
|
|
133
|
+
for person in people_to_print:
|
|
134
|
+
if person.name in epstein_files.uninteresting_emailers() and not args.names:
|
|
135
|
+
continue
|
|
104
136
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
num_emails_printed_since_last_color_key += len(author_emails)
|
|
137
|
+
printed_person_emails = person.print_emails()
|
|
138
|
+
printed_emails.extend(printed_person_emails)
|
|
139
|
+
num_emails_printed_since_last_color_key += len(printed_person_emails)
|
|
109
140
|
|
|
110
141
|
# Print color key every once in a while
|
|
111
142
|
if num_emails_printed_since_last_color_key > PRINT_COLOR_KEY_EVERY_N_EMAILS:
|
|
@@ -113,26 +144,28 @@ def print_emails_section(epstein_files: EpsteinFiles) -> list[Email]:
|
|
|
113
144
|
num_emails_printed_since_last_color_key = 0
|
|
114
145
|
|
|
115
146
|
if args.names:
|
|
116
|
-
return
|
|
147
|
+
return printed_emails
|
|
117
148
|
|
|
118
149
|
# Print other interesting emails
|
|
119
|
-
|
|
120
|
-
extra_emails = [e for e in epstein_files.for_ids(INTERESTING_EMAIL_IDS) if e.file_id not in
|
|
121
|
-
print_subtitle_panel(OTHER_INTERESTING_EMAILS_SUBTITLE)
|
|
122
|
-
console.line()
|
|
150
|
+
printed_email_ids = [email.file_id for email in printed_emails]
|
|
151
|
+
extra_emails = [e for e in epstein_files.for_ids(INTERESTING_EMAIL_IDS) if e.file_id not in printed_email_ids]
|
|
123
152
|
|
|
124
|
-
|
|
125
|
-
|
|
153
|
+
if len(extra_emails) > 0:
|
|
154
|
+
print_subtitle_panel(OTHER_INTERESTING_EMAILS_SUBTITLE)
|
|
155
|
+
console.line()
|
|
126
156
|
|
|
127
|
-
|
|
157
|
+
for other_email in extra_emails:
|
|
158
|
+
console.print(other_email)
|
|
159
|
+
printed_emails.append(cast(Email, other_email))
|
|
128
160
|
|
|
129
161
|
if args.all_emails:
|
|
130
|
-
_verify_all_emails_were_printed(epstein_files,
|
|
162
|
+
_verify_all_emails_were_printed(epstein_files, printed_emails)
|
|
131
163
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
164
|
+
_print_email_device_signature_info(epstein_files)
|
|
165
|
+
fwded_articles = [e for e in printed_emails if e.config and e.is_fwded_article()]
|
|
166
|
+
log_msg = f"Rewrote {len(Email.rewritten_header_ids)} of {len(printed_emails)} email headers"
|
|
167
|
+
logger.warning(f" -> {log_msg}, {len(fwded_articles)} of the Emails printed were forwarded articles.")
|
|
168
|
+
return printed_emails
|
|
136
169
|
|
|
137
170
|
|
|
138
171
|
def print_json_files(epstein_files: EpsteinFiles):
|
|
@@ -165,31 +198,39 @@ def print_json_stats(epstein_files: EpsteinFiles) -> None:
|
|
|
165
198
|
console.line(5)
|
|
166
199
|
console.print(Panel('JSON Stats Dump', expand=True, style='reverse bold'), '\n')
|
|
167
200
|
print_json(f"MessengerLog Sender Counts", MessengerLog.count_authors(epstein_files.imessage_logs), skip_falsey=True)
|
|
168
|
-
print_json(f"Email Author Counts", epstein_files.email_author_counts, skip_falsey=True)
|
|
169
|
-
print_json(f"Email Recipient Counts", epstein_files.email_recipient_counts, skip_falsey=True)
|
|
201
|
+
print_json(f"Email Author Counts", epstein_files.email_author_counts(), skip_falsey=True)
|
|
202
|
+
print_json(f"Email Recipient Counts", epstein_files.email_recipient_counts(), skip_falsey=True)
|
|
170
203
|
print_json("Email signature_substitution_countss", epstein_files.email_signature_substitution_counts(), skip_falsey=True)
|
|
171
|
-
print_json("email_author_device_signatures", dict_sets_to_lists(epstein_files.email_authors_to_device_signatures))
|
|
172
|
-
print_json("email_sent_from_devices", dict_sets_to_lists(epstein_files.email_device_signatures_to_authors))
|
|
173
|
-
print_json("
|
|
204
|
+
print_json("email_author_device_signatures", dict_sets_to_lists(epstein_files.email_authors_to_device_signatures()))
|
|
205
|
+
print_json("email_sent_from_devices", dict_sets_to_lists(epstein_files.email_device_signatures_to_authors()))
|
|
206
|
+
print_json("unknown_recipient_ids", epstein_files.unknown_recipient_ids())
|
|
174
207
|
print_json("count_by_month", count_by_month(epstein_files.all_documents()))
|
|
175
208
|
|
|
176
209
|
|
|
177
|
-
def print_other_files_section(
|
|
210
|
+
def print_other_files_section(epstein_files: EpsteinFiles) -> list[OtherFile]:
|
|
178
211
|
"""Returns the OtherFile objects that were interesting enough to print."""
|
|
212
|
+
if args.uninteresting:
|
|
213
|
+
files = [f for f in epstein_files.other_files if not f.is_interesting()]
|
|
214
|
+
else:
|
|
215
|
+
files = [f for f in epstein_files.other_files if args.all_other_files or f.is_interesting()]
|
|
216
|
+
|
|
179
217
|
title_pfx = '' if args.all_other_files else 'Selected '
|
|
180
|
-
category_table = OtherFile.
|
|
218
|
+
category_table = OtherFile.summary_table(files, title_pfx=title_pfx)
|
|
181
219
|
other_files_preview_table = OtherFile.files_preview_table(files, title_pfx=title_pfx)
|
|
182
220
|
print_section_header(f"{FIRST_FEW_LINES} of {len(files)} {title_pfx}Files That Are Neither Emails Nor Text Messages")
|
|
183
221
|
print_other_page_link(epstein_files)
|
|
184
222
|
print_centered(Padding(category_table, (2, 0)))
|
|
185
223
|
console.print(other_files_preview_table)
|
|
224
|
+
return files
|
|
186
225
|
|
|
187
226
|
|
|
188
|
-
def print_text_messages_section(
|
|
227
|
+
def print_text_messages_section(epstein_files: EpsteinFiles) -> list[MessengerLog]:
|
|
189
228
|
"""Print summary table and stats for text messages."""
|
|
229
|
+
imessage_logs = [log for log in epstein_files.imessage_logs if not args.names or log.author in args.names]
|
|
230
|
+
|
|
190
231
|
if not imessage_logs:
|
|
191
|
-
logger.warning(f"No
|
|
192
|
-
return
|
|
232
|
+
logger.warning(f"No MessengerLogs found for {args.names}")
|
|
233
|
+
return imessage_logs
|
|
193
234
|
|
|
194
235
|
print_section_header('All of His Text Messages')
|
|
195
236
|
print_centered("(conversations are sorted chronologically based on timestamp of first message in the log file)", style='dim')
|
|
@@ -203,6 +244,8 @@ def print_text_messages_section(imessage_logs: list[MessengerLog]) -> None:
|
|
|
203
244
|
console.print(Padding(log_file))
|
|
204
245
|
console.line(2)
|
|
205
246
|
|
|
247
|
+
return imessage_logs
|
|
248
|
+
|
|
206
249
|
|
|
207
250
|
def write_urls() -> None:
|
|
208
251
|
"""Write _URL style constant variables to URLS_ENV file so bash scripts can load as env vars."""
|
|
@@ -226,113 +269,26 @@ def write_urls() -> None:
|
|
|
226
269
|
logger.warning(f"Wrote {len(url_vars)} URL variables to '{URLS_ENV}'\n")
|
|
227
270
|
|
|
228
271
|
|
|
229
|
-
def
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
emailer_counts = {
|
|
250
|
-
emailer: epstein_files.email_author_counts[emailer] + epstein_files.email_recipient_counts[emailer]
|
|
251
|
-
for emailer in epstein_files.all_emailers(include_useless=True)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
for name, count in sort_dict(emailer_counts):
|
|
255
|
-
style = get_style_for_name(name, default_style=DEFAULT_NAME_STYLE)
|
|
256
|
-
emails = epstein_files.emails_for(name)
|
|
257
|
-
|
|
258
|
-
counts_table.add_row(
|
|
259
|
-
Text.from_markup(link_markup(epsteinify_name_url(name or UNKNOWN), name or UNKNOWN, style)),
|
|
260
|
-
f"{count:,}",
|
|
261
|
-
str(epstein_files.email_author_counts[name]),
|
|
262
|
-
str(epstein_files.email_recipient_counts[name]),
|
|
263
|
-
emails[0].date_str(),
|
|
264
|
-
emails[-1].date_str(),
|
|
265
|
-
f"{epstein_files.email_conversation_length_in_days(name)}",
|
|
266
|
-
link_text_obj(search_jmail_url(name), JMAIL) if name else '',
|
|
267
|
-
link_text_obj(epstein_media_person_url(name), EPSTEIN_MEDIA) if _is_ok_for_epstein_web(name) else '',
|
|
268
|
-
link_text_obj(epstein_web_person_url(name), EPSTEIN_WEB) if _is_ok_for_epstein_web(name) else '',
|
|
269
|
-
link_text_obj(search_twitter_url(name), 'search X') if name else '',
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
return counts_table
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _is_ok_for_epstein_web(name: str | None) -> bool:
|
|
276
|
-
"""Return True if it's likely that EpsteinWeb has a page for this name."""
|
|
277
|
-
if name is None or ' ' not in name:
|
|
278
|
-
return False
|
|
279
|
-
elif '@' in name or '/' in name or '??' in name:
|
|
280
|
-
return False
|
|
281
|
-
elif name in INVALID_FOR_EPSTEIN_WEB:
|
|
282
|
-
return False
|
|
283
|
-
|
|
284
|
-
return True
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _table_of_selected_emailers(_list: list[str | None], epstein_files: EpsteinFiles) -> Table:
|
|
288
|
-
"""Add the first emailed_at timestamp for each emailer if 'epstein_files' provided."""
|
|
289
|
-
header_pfx = '' if args.all_emails else 'Selected '
|
|
290
|
-
table = build_table(f'{header_pfx}Email Conversations Grouped by Counterparty Will Appear in this Order')
|
|
291
|
-
table.add_column('Start Date')
|
|
292
|
-
table.add_column('Name', max_width=25, no_wrap=True)
|
|
293
|
-
table.add_column('Category', justify='center', style='dim italic')
|
|
294
|
-
table.add_column('Num', justify='right', style='wheat4')
|
|
295
|
-
table.add_column('Info', style='white italic')
|
|
296
|
-
current_year = 1990
|
|
297
|
-
current_year_month = current_year * 12
|
|
298
|
-
grey_idx = 0
|
|
299
|
-
|
|
300
|
-
for i, name in enumerate(_list):
|
|
301
|
-
earliest_email_date = (epstein_files.earliest_email_at(name)).date()
|
|
302
|
-
year_months = (earliest_email_date.year * 12) + earliest_email_date.month
|
|
303
|
-
|
|
304
|
-
# Color year rollovers more brightly
|
|
305
|
-
if current_year != earliest_email_date.year:
|
|
306
|
-
grey_idx = 0
|
|
307
|
-
elif current_year_month != year_months:
|
|
308
|
-
grey_idx = ((current_year_month - 1) % 12) + 1
|
|
309
|
-
|
|
310
|
-
current_year_month = year_months
|
|
311
|
-
current_year = earliest_email_date.year
|
|
312
|
-
category = get_category_txt_for_name(name)
|
|
313
|
-
info = get_info_for_name(name)
|
|
314
|
-
style = get_style_for_name(name, default_style='none')
|
|
315
|
-
|
|
316
|
-
if name == JEFFREY_EPSTEIN:
|
|
317
|
-
info = Text('(emails sent by Epstein to himself that would not otherwise be printed)', style=ALT_INFO_STYLE)
|
|
318
|
-
if category and category.plain == 'paula': # TODO: hacky
|
|
319
|
-
category = None
|
|
320
|
-
elif category and info:
|
|
321
|
-
info = info.removeprefix(f"{category.plain}, ").removeprefix(category.plain)
|
|
322
|
-
elif not name:
|
|
323
|
-
info = Text('(emails whose author or recipient could not be determined)', style=ALT_INFO_STYLE)
|
|
324
|
-
elif style == 'none' and '@' not in name and not (category or info):
|
|
325
|
-
info = QUESTION_MARKS_TXT
|
|
326
|
-
|
|
327
|
-
table.add_row(
|
|
328
|
-
Text(str(earliest_email_date), style=f"grey{GREY_NUMBERS[grey_idx]}"),
|
|
329
|
-
styled_name(name, default_style='dim'),
|
|
330
|
-
category,
|
|
331
|
-
f"{len(epstein_files.emails_for(name)):,}",
|
|
332
|
-
info or '',
|
|
333
|
-
)
|
|
334
|
-
|
|
335
|
-
return table
|
|
272
|
+
def _print_email_device_signature_info(epstein_files: EpsteinFiles) -> None:
|
|
273
|
+
print_subtitle_panel(DEVICE_SIGNATURE_SUBTITLE)
|
|
274
|
+
console.print(_signature_table(epstein_files.email_device_signatures_to_authors(), (DEVICE_SIGNATURE, AUTHOR), ', '))
|
|
275
|
+
console.print(_signature_table(epstein_files.email_authors_to_device_signatures(), (AUTHOR, DEVICE_SIGNATURE)))
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def _signature_table(keyed_sets: dict[str, set[str]], cols: tuple[str, str], join_char: str = '\n') -> Padding:
|
|
279
|
+
"""Build table for who signed emails with 'Sent from my iPhone' etc."""
|
|
280
|
+
title = 'Email Signatures Used By Authors' if cols[0] == AUTHOR else 'Authors Seen Using Email Signatures'
|
|
281
|
+
table = build_table(title, header_style="bold reverse", show_lines=True)
|
|
282
|
+
|
|
283
|
+
for i, col in enumerate(cols):
|
|
284
|
+
table.add_column(col.title() + ('s' if i == 1 else ''))
|
|
285
|
+
|
|
286
|
+
new_dict = dict_sets_to_lists(keyed_sets)
|
|
287
|
+
|
|
288
|
+
for k in sorted(new_dict.keys()):
|
|
289
|
+
table.add_row(highlighter(k or UNKNOWN), highlighter(join_char.join(sorted(new_dict[k]))))
|
|
290
|
+
|
|
291
|
+
return Padding(table, DEVICE_SIGNATURE_PADDING)
|
|
336
292
|
|
|
337
293
|
|
|
338
294
|
def _verify_all_emails_were_printed(epstein_files: EpsteinFiles, already_printed_emails: list[Email]) -> None:
|
epstein_files/util/rich.py
CHANGED
|
@@ -18,7 +18,7 @@ from epstein_files.util.constant.names import UNKNOWN
|
|
|
18
18
|
from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, TEXT_MESSAGE
|
|
19
19
|
from epstein_files.util.constant.urls import *
|
|
20
20
|
from epstein_files.util.constants import HEADER_ABBREVIATIONS
|
|
21
|
-
from epstein_files.util.data import json_safe
|
|
21
|
+
from epstein_files.util.data import json_safe
|
|
22
22
|
from epstein_files.util.env import args
|
|
23
23
|
from epstein_files.util.file_helper import log_file_write
|
|
24
24
|
from epstein_files.util.highlighted_group import ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter
|
|
@@ -26,11 +26,11 @@ from epstein_files.util.logging import logger
|
|
|
26
26
|
|
|
27
27
|
TITLE_WIDTH = 50
|
|
28
28
|
SUBTITLE_WIDTH = 110
|
|
29
|
-
MIN_AUTHOR_PANEL_WIDTH = 80
|
|
30
29
|
NUM_COLOR_KEY_COLS = 4
|
|
31
30
|
NA_TXT = Text(NA, style='dim')
|
|
32
31
|
SUBTITLE_PADDING = (2, 0, 1, 0)
|
|
33
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]
|
|
34
34
|
|
|
35
35
|
INFO_STYLE = 'white dim italic'
|
|
36
36
|
KEY_STYLE = 'honeydew2 bold'
|
|
@@ -70,7 +70,7 @@ CONSOLE_ARGS = {
|
|
|
70
70
|
'color_system': '256',
|
|
71
71
|
'highlighter': EpsteinHighlighter(),
|
|
72
72
|
'record': args.build,
|
|
73
|
-
'safe_box':
|
|
73
|
+
'safe_box': True,
|
|
74
74
|
'theme': Theme(THEME_STYLES),
|
|
75
75
|
'width': args.width,
|
|
76
76
|
}
|
|
@@ -83,21 +83,21 @@ console = Console(**CONSOLE_ARGS)
|
|
|
83
83
|
highlighter = CONSOLE_ARGS['highlighter']
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
def add_cols_to_table(table: Table,
|
|
86
|
+
def add_cols_to_table(table: Table, cols: list[str | dict], justify: str = 'center') -> None:
|
|
87
87
|
"""Left most col will be left justified, rest are center justified."""
|
|
88
|
-
for i, col in enumerate(
|
|
89
|
-
|
|
88
|
+
for i, col in enumerate(cols):
|
|
89
|
+
col_justify = 'left' if i == 0 else justify
|
|
90
90
|
|
|
91
91
|
if isinstance(col, dict):
|
|
92
92
|
col_name = col['name']
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
del
|
|
93
|
+
col_kwargs = deepcopy(col)
|
|
94
|
+
col_kwargs['justify'] = col_kwargs.get('justify', col_justify)
|
|
95
|
+
del col_kwargs['name']
|
|
96
96
|
else:
|
|
97
97
|
col_name = col
|
|
98
|
-
|
|
98
|
+
col_kwargs = {'justify': col_justify}
|
|
99
99
|
|
|
100
|
-
table.add_column(col_name, **
|
|
100
|
+
table.add_column(col_name, **col_kwargs)
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def build_highlighter(pattern: str) -> EpsteinHighlighter:
|
|
@@ -108,7 +108,7 @@ def build_highlighter(pattern: str) -> EpsteinHighlighter:
|
|
|
108
108
|
return TempHighlighter()
|
|
109
109
|
|
|
110
110
|
|
|
111
|
-
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:
|
|
112
112
|
table = Table(title=title, **{**DEFAULT_TABLE_KWARGS, **kwargs})
|
|
113
113
|
|
|
114
114
|
if cols:
|
|
@@ -149,19 +149,6 @@ def parenthesize(msg: str | Text, style: str = '') -> Text:
|
|
|
149
149
|
return Text('(', style=style).append(txt).append(')')
|
|
150
150
|
|
|
151
151
|
|
|
152
|
-
def print_author_panel(msg: str, footer: str | None, style: str | None) -> None:
|
|
153
|
-
"""Print a panel with the name of an emailer and a few tidbits of information about them."""
|
|
154
|
-
style = 'white' if (not style or style == DEFAULT) else style
|
|
155
|
-
panel_style = f"black on {style} bold"
|
|
156
|
-
width = max(MIN_AUTHOR_PANEL_WIDTH, len(msg) + 4, len(footer or '') + 8)
|
|
157
|
-
elements: list[RenderableType] = [Panel(Text(msg, justify='center'), width=width, style=panel_style)]
|
|
158
|
-
|
|
159
|
-
if footer:
|
|
160
|
-
elements.append(Text(f"({footer})", justify='center', style=f"{style} italic"))
|
|
161
|
-
|
|
162
|
-
print_centered(Padding(Group(*elements), (2, 0, 1, 0)))
|
|
163
|
-
|
|
164
|
-
|
|
165
152
|
def print_centered(obj: RenderableType, style: str = '') -> None:
|
|
166
153
|
console.print(Align.center(obj), style=style)
|
|
167
154
|
|
|
@@ -200,7 +187,7 @@ def print_title_page_header() -> None:
|
|
|
200
187
|
Text.from_markup(link_markup(other_site_url(), other_site_msg, f"{OTHER_SITE_LINK_STYLE} bold")),
|
|
201
188
|
link_text_obj(WORD_COUNT_URL, 'most frequently used words in the emails and texts', AUX_SITE_LINK_STYLE),
|
|
202
189
|
link_text_obj(JSON_METADATA_URL, 'author attribution explanations', AUX_SITE_LINK_STYLE),
|
|
203
|
-
link_text_obj(
|
|
190
|
+
link_text_obj(JSON_FILES_URL, "epstein's json files", AUX_SITE_LINK_STYLE),
|
|
204
191
|
]
|
|
205
192
|
|
|
206
193
|
for link in links:
|
|
@@ -212,7 +199,8 @@ def print_title_page_tables(epstein_files: 'EpsteinFiles') -> None:
|
|
|
212
199
|
_print_external_links()
|
|
213
200
|
console.line()
|
|
214
201
|
_print_abbreviations_table()
|
|
215
|
-
epstein_files.
|
|
202
|
+
print_centered(epstein_files.overview_table())
|
|
203
|
+
console.line()
|
|
216
204
|
print_color_key()
|
|
217
205
|
print_centered(f"if you think there's an attribution error or can deanonymize an {UNKNOWN} contact {CRYPTADAMUS_TWITTER}", 'grey46')
|
|
218
206
|
print_centered('note this site is based on the OCR text provided by Congress which is not always the greatest', 'grey23')
|
|
@@ -234,13 +222,11 @@ def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
|
|
|
234
222
|
|
|
235
223
|
|
|
236
224
|
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
225
|
if other_site_type() == EMAIL:
|
|
240
|
-
txt = Text
|
|
226
|
+
txt = THE_OTHER_PAGE_TXT + Text(f' is uncurated and has all {len(epstein_files.emails):,} emails')
|
|
241
227
|
txt.append(f" and {len(epstein_files.other_files)} unclassifiable files")
|
|
242
228
|
else:
|
|
243
|
-
txt =
|
|
229
|
+
txt = THE_OTHER_PAGE_TXT + (f' displays a limited collection of emails and')
|
|
244
230
|
txt.append(" unclassifiable files of particular interest")
|
|
245
231
|
|
|
246
232
|
print_centered(parenthesize(txt), style=OTHER_PAGE_MSG_STYLE)
|
|
@@ -267,7 +253,7 @@ def print_subtitle_panel(msg: str, style: str = 'black on white') -> None:
|
|
|
267
253
|
def print_section_header(msg: str, style: str = SECTION_HEADER_STYLE, is_centered: bool = False) -> None:
|
|
268
254
|
panel = Panel(Text(msg, justify='center'), expand=True, padding=(1, 1), style=style)
|
|
269
255
|
panel = Align.center(panel) if is_centered else panel
|
|
270
|
-
console.print(Padding(panel, (3,
|
|
256
|
+
console.print(Padding(panel, (3, 5, 1, 5)))
|
|
271
257
|
|
|
272
258
|
|
|
273
259
|
def print_starred_header(msg: str, num_stars: int = 7, num_spaces: int = 2, style: str = TITLE_STYLE) -> None:
|
|
@@ -311,7 +297,7 @@ def write_html(output_path: Path | None) -> None:
|
|
|
311
297
|
def _print_abbreviations_table() -> None:
|
|
312
298
|
table = build_table(title="Abbreviations Used Frequently In These Conversations", show_header=False)
|
|
313
299
|
table.add_column("Abbreviation", justify="center", style='bold')
|
|
314
|
-
table.add_column("Translation",
|
|
300
|
+
table.add_column("Translation", justify="center", min_width=62, style="white")
|
|
315
301
|
|
|
316
302
|
for k, v in HEADER_ABBREVIATIONS.items():
|
|
317
303
|
table.add_row(highlighter(k), v)
|
|
@@ -326,8 +312,7 @@ def _print_external_links() -> None:
|
|
|
326
312
|
raw_docs_link = join_texts([link_text_obj(RAW_OVERSIGHT_DOCS_GOOGLE_DRIVE_URL, 'raw files', style=f"{ARCHIVE_LINK_COLOR} dim")], encloser='()')
|
|
327
313
|
print_centered(join_texts([presser_link, raw_docs_link]))
|
|
328
314
|
print_centered(link_markup(JMAIL_URL, JMAIL) + " (read His Emails via Gmail interface)")
|
|
329
|
-
print_centered(link_markup(
|
|
330
|
-
print_centered(link_markup(COURIER_NEWSROOM_ARCHIVE_URL, 'Searchable Archive') + " (Courier Newsroom)")
|
|
315
|
+
print_centered(link_markup(EPSTEIN_DOCS_URL) + " (searchable archive)")
|
|
331
316
|
print_centered(link_markup(EPSTEINIFY_URL) + " (raw document images)")
|
|
332
317
|
print_centered(link_markup(EPSTEIN_WEB_URL) + " (character summaries)")
|
|
333
318
|
print_centered(link_markup(EPSTEIN_MEDIA_URL) + " (raw document images)")
|
epstein_files/util/timer.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Type
|
|
3
4
|
|
|
4
5
|
from epstein_files.util.logging import logger
|
|
5
6
|
|
|
@@ -10,6 +11,19 @@ class Timer:
|
|
|
10
11
|
checkpoint_at: float = field(default_factory=lambda: time.perf_counter())
|
|
11
12
|
decimals: int = 2
|
|
12
13
|
|
|
14
|
+
def log_section_complete(self, label: str, all_docs: list, printed_docs: list) -> None:
|
|
15
|
+
num_skipped = len(all_docs) - len(printed_docs)
|
|
16
|
+
prefix = suffix = ''
|
|
17
|
+
|
|
18
|
+
if num_skipped == 0:
|
|
19
|
+
prefix = 'all '
|
|
20
|
+
elif num_skipped < 0:
|
|
21
|
+
suffix = f"(at least {num_skipped} {label}s printed more than once)"
|
|
22
|
+
else:
|
|
23
|
+
suffix = f"(skipped {num_skipped})"
|
|
24
|
+
|
|
25
|
+
self.print_at_checkpoint(f"Printed {prefix}{len(printed_docs)} {label}s {suffix}".strip())
|
|
26
|
+
|
|
13
27
|
def print_at_checkpoint(self, msg: str) -> None:
|
|
14
28
|
logger.warning(f"{msg} in {self.seconds_since_checkpoint_str()}...")
|
|
15
29
|
self.checkpoint_at = time.perf_counter()
|
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:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: epstein-files
|
|
3
|
-
Version: 1.1
|
|
3
|
+
Version: 1.2.1
|
|
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)
|
|
@@ -31,7 +32,7 @@ Project-URL: TextMessages, https://michelcrypt4d4mus.github.io/epstein_text_mess
|
|
|
31
32
|
Project-URL: WordCounts, https://michelcrypt4d4mus.github.io/epstein_text_messages/communication_word_count_epstein_files_nov_2025.html
|
|
32
33
|
Description-Content-Type: text/markdown
|
|
33
34
|
|
|
34
|
-
#
|
|
35
|
+
# Color Highlighted Epstein Emails and Text Messages
|
|
35
36
|
|
|
36
37
|

|
|
37
38
|
|
|
@@ -119,3 +120,6 @@ for file in epstein_files.other_files:
|
|
|
119
120
|
do_stuff(file)
|
|
120
121
|
```
|
|
121
122
|
|
|
123
|
+
# Everyone Who Sent or Received an Email in the November Document Dump
|
|
124
|
+

|
|
125
|
+
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
epstein_files/__init__.py,sha256=zhFnPc-TFQLJIO0qnXJzDM1Bf_u8nv308GDKAvn405g,4831
|
|
2
|
+
epstein_files/documents/communication.py,sha256=QiCZ35R2ttlqcdovm5LBdGqgoj4xCcpl9ANqwlA9XGU,1752
|
|
3
|
+
epstein_files/documents/document.py,sha256=YwWHaiccKpYiS4UJAiGzDviyYV2hzdeee_vdX4ybhOo,19519
|
|
4
|
+
epstein_files/documents/email.py,sha256=xc2cv6KkMfKhNLMfqW6rmDDXn1ozRChao1i98-0byI8,42297
|
|
5
|
+
epstein_files/documents/emails/email_header.py,sha256=3UD2pXMS9bRsFP4L5RSP2tSjI8OR6lq6gPKKao0DYLY,7739
|
|
6
|
+
epstein_files/documents/imessage/text_message.py,sha256=IexwwVeF14FZqD7IuK47bIHYam07ZcD3rxheVNULNkc,3394
|
|
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=xZSILlnYlX2QIYCQCm6WNHvNqhF5do6AmxT7qxw-yog,9417
|
|
10
|
+
epstein_files/epstein_files.py,sha256=D2yMCm1IM5Skojzvvlod6yTGI_nUtNQmwmX6dzv2Egs,14057
|
|
11
|
+
epstein_files/person.py,sha256=pESfBu2VvsLtrA86V8_D2BHKtb4m0F4xCf7tIrwyp6c,13579
|
|
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=pkS1BWBxpxveHRrL1Z4muaXcK2ICIwM7RFGCsCf7Qls,11267
|
|
15
|
+
epstein_files/util/constant/output_files.py,sha256=gUZJ4mNoeJy3qTYWr_jhSmQI-_uV_jdLR0YCiaQd_Qg,1982
|
|
16
|
+
epstein_files/util/constant/strings.py,sha256=z_vwWxWU-_85rDTbVyo8pbi-5keBYmYnbh758n2LebU,1989
|
|
17
|
+
epstein_files/util/constant/urls.py,sha256=UvhQP6sWqZaNnkqXFIn44oVmYLWFA-A0FGpo40Ds-MM,5651
|
|
18
|
+
epstein_files/util/constants.py,sha256=VdOOp1azsaFgeEEO9UMS8hx5NXNP4pozfyH2IA6kW9E,121794
|
|
19
|
+
epstein_files/util/data.py,sha256=0kCGgs3Cscbn1Q9G82bSB51CKnQpz1iUzMJoBBM34IE,2964
|
|
20
|
+
epstein_files/util/doc_cfg.py,sha256=gIBAoeheQYqqrCtVRU2ytYu799LyCiPxPyHDgWUad3c,9471
|
|
21
|
+
epstein_files/util/env.py,sha256=bSf2PKkOukN7LA8YdrWQS7XTRY0pLTNXo8OiQ1EKJDE,6722
|
|
22
|
+
epstein_files/util/file_helper.py,sha256=MpG1hI7DGs05fV9KSVb_ltc98DC8tv1E_TTo5X_E7Js,3010
|
|
23
|
+
epstein_files/util/highlighted_group.py,sha256=8uuQAHIBk7c5VYzg2xqfHsR-TGKfX7f1qdwB74UfTfI,56719
|
|
24
|
+
epstein_files/util/logging.py,sha256=pSLHGrUnPQGFMGYNIOTbZbCyy5dDOCbNrbNz22wIcZI,2099
|
|
25
|
+
epstein_files/util/output.py,sha256=pqTu6MApTiRKc_Q0mZzKD73UAXnAbbA5zWzGijAH4OU,12681
|
|
26
|
+
epstein_files/util/rich.py,sha256=z9wbvNQbOJqNWm7IDBNJFgRzMP2X5CTmOgwrOkuSEJk,13639
|
|
27
|
+
epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
|
|
28
|
+
epstein_files/util/timer.py,sha256=GZHefXiPA-prK-1szC1cebl44Y-i4Wd3K8zedFpcggg,1373
|
|
29
|
+
epstein_files/util/word_count.py,sha256=rs-bsSMnGG1BnYUmxYuBLZ8X0-3-jJdaB03v1jOIq4g,9202
|
|
30
|
+
epstein_files-1.2.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
31
|
+
epstein_files-1.2.1.dist-info/METADATA,sha256=_NTxSxgHoHb1uPeRlLqgxBl_9wJnyMsY2_3icXgZTDw,6222
|
|
32
|
+
epstein_files-1.2.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
33
|
+
epstein_files-1.2.1.dist-info/entry_points.txt,sha256=5qYgwAXpxegeAicD_rzda_trDRnUC51F5UVDpcZ7j6Q,240
|
|
34
|
+
epstein_files-1.2.1.dist-info/RECORD,,
|