epstein-files 1.0.0__py3-none-any.whl → 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epstein_files/__init__.py +75 -135
- epstein_files/documents/communication.py +9 -9
- epstein_files/documents/document.py +115 -87
- epstein_files/documents/email.py +154 -85
- epstein_files/documents/emails/email_header.py +7 -6
- epstein_files/documents/imessage/text_message.py +3 -2
- epstein_files/documents/json_file.py +17 -0
- epstein_files/documents/messenger_log.py +62 -3
- epstein_files/documents/other_file.py +165 -17
- epstein_files/epstein_files.py +128 -169
- epstein_files/util/constant/names.py +8 -1
- epstein_files/util/constant/output_files.py +29 -0
- epstein_files/util/constant/strings.py +27 -0
- epstein_files/util/constant/urls.py +25 -9
- epstein_files/util/constants.py +1018 -1045
- epstein_files/util/data.py +20 -55
- epstein_files/util/{file_cfg.py → doc_cfg.py} +121 -43
- epstein_files/util/env.py +19 -20
- epstein_files/util/file_helper.py +38 -21
- epstein_files/util/highlighted_group.py +229 -177
- epstein_files/util/logging.py +63 -0
- epstein_files/util/output.py +180 -0
- epstein_files/util/rich.py +29 -17
- epstein_files/util/search_result.py +14 -6
- epstein_files/util/timer.py +24 -0
- epstein_files/util/word_count.py +2 -1
- {epstein_files-1.0.0.dist-info → epstein_files-1.0.2.dist-info}/METADATA +20 -4
- epstein_files-1.0.2.dist-info/RECORD +33 -0
- epstein_files-1.0.2.dist-info/entry_points.txt +7 -0
- epstein_files-1.0.0.dist-info/RECORD +0 -28
- {epstein_files-1.0.0.dist-info → epstein_files-1.0.2.dist-info}/LICENSE +0 -0
- {epstein_files-1.0.0.dist-info → epstein_files-1.0.2.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from os import environ
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from rich.console import Console
|
|
6
|
+
from rich.highlighter import ReprHighlighter
|
|
7
|
+
from rich.logging import RichHandler
|
|
8
|
+
from rich.theme import Theme
|
|
9
|
+
|
|
10
|
+
from epstein_files.util.constant.strings import *
|
|
11
|
+
from epstein_files.util.file_helper import file_size_str
|
|
12
|
+
|
|
13
|
+
FILENAME_STYLE = 'gray27'
|
|
14
|
+
|
|
15
|
+
DOC_TYPE_STYLES = {
|
|
16
|
+
DOCUMENT_CLASS: 'grey69',
|
|
17
|
+
EMAIL_CLASS: 'dark_orange3',
|
|
18
|
+
JSON_FILE_CLASS: 'sandy_brown',
|
|
19
|
+
MESSENGER_LOG_CLASS: 'deep_pink4',
|
|
20
|
+
OTHER_FILE_CLASS: 'grey69',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
LOG_THEME = {
|
|
24
|
+
f"{ReprHighlighter.base_style}{doc_type}": f"{doc_style} bold"
|
|
25
|
+
for doc_type, doc_style in DOC_TYPE_STYLES.items()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
LOG_THEME[f"{ReprHighlighter.base_style}epstein_filename"] = FILENAME_STYLE
|
|
29
|
+
LOG_LEVEL_ENV_VAR = 'LOG_LEVEL'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class LogHighlighter(ReprHighlighter):
|
|
33
|
+
highlights = ReprHighlighter.highlights + [
|
|
34
|
+
*[fr"(?P<{doc_type}>{doc_type})" for doc_type in DOC_TYPE_STYLES.keys()],
|
|
35
|
+
"(?P<epstein_filename>" + FILE_NAME_REGEX.pattern + ')',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
log_console = Console(color_system='256', theme=Theme(LOG_THEME))
|
|
40
|
+
log_handler = RichHandler(console=log_console, highlighter=LogHighlighter())
|
|
41
|
+
logging.basicConfig(level="NOTSET", format="%(message)s", datefmt="[%X]", handlers=[log_handler])
|
|
42
|
+
logger = logging.getLogger("rich")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Log levels
|
|
46
|
+
datefinder_logger = logging.getLogger('datefinder') # Set log level to suppress annoying output
|
|
47
|
+
env_log_level_str = environ.get(LOG_LEVEL_ENV_VAR) or None
|
|
48
|
+
env_log_level = None
|
|
49
|
+
|
|
50
|
+
if env_log_level_str:
|
|
51
|
+
try:
|
|
52
|
+
env_log_level = getattr(logging, env_log_level_str)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logger.warning(f"{LOG_LEVEL_ENV_VAR}='{env_log_level_str}' does not exist, defaulting to DEBUG")
|
|
55
|
+
env_log_level = logging.DEBUG
|
|
56
|
+
|
|
57
|
+
logger.warning(f"Setting log level to {env_log_level} based on {LOG_LEVEL_ENV_VAR} env var...")
|
|
58
|
+
logger.setLevel(env_log_level)
|
|
59
|
+
datefinder_logger.setLevel(env_log_level)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def log_file_write(file_path: str | Path) -> None:
|
|
63
|
+
logger.warning(f"Wrote {file_size_str(file_path)} to '{file_path}'")
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from rich.padding import Padding
|
|
2
|
+
|
|
3
|
+
from epstein_files.documents.email import Email
|
|
4
|
+
from epstein_files.documents.messenger_log import MessengerLog
|
|
5
|
+
from epstein_files.epstein_files import EpsteinFiles, count_by_month
|
|
6
|
+
from epstein_files.util.constant.output_files import JSON_METADATA_PATH
|
|
7
|
+
from epstein_files.util.constant import urls
|
|
8
|
+
from epstein_files.util.constant.html import *
|
|
9
|
+
from epstein_files.util.constant.names import *
|
|
10
|
+
from epstein_files.util.constant.strings import EMAIL_CLASS, MESSENGER_LOG_CLASS
|
|
11
|
+
from epstein_files.util.data import dict_sets_to_lists
|
|
12
|
+
from epstein_files.util.env import args, specified_names
|
|
13
|
+
from epstein_files.util.logging import log_file_write, logger
|
|
14
|
+
from epstein_files.util.rich import *
|
|
15
|
+
|
|
16
|
+
PRINT_COLOR_KEY_EVERY_N_EMAILS = 150
|
|
17
|
+
|
|
18
|
+
# Order matters. Default names to print emails for.
|
|
19
|
+
DEFAULT_EMAILERS = [
|
|
20
|
+
JEREMY_RUBIN,
|
|
21
|
+
AL_SECKEL,
|
|
22
|
+
JOI_ITO,
|
|
23
|
+
JABOR_Y,
|
|
24
|
+
STEVEN_SINOFSKY,
|
|
25
|
+
DANIEL_SIAD,
|
|
26
|
+
JEAN_LUC_BRUNEL,
|
|
27
|
+
STEVEN_HOFFENBERG,
|
|
28
|
+
EHUD_BARAK,
|
|
29
|
+
MARTIN_NOWAK,
|
|
30
|
+
MASHA_DROKOVA,
|
|
31
|
+
RENATA_BOLOTOVA,
|
|
32
|
+
STEVE_BANNON,
|
|
33
|
+
OLIVIER_COLOM,
|
|
34
|
+
BORIS_NIKOLIC,
|
|
35
|
+
PRINCE_ANDREW,
|
|
36
|
+
JIDE_ZEITLIN,
|
|
37
|
+
DAVID_STERN,
|
|
38
|
+
MOHAMED_WAHEED_HASSAN,
|
|
39
|
+
JENNIFER_JACQUET,
|
|
40
|
+
TYLER_SHEARS,
|
|
41
|
+
CHRISTINA_GALBRAITH,
|
|
42
|
+
None,
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
# Order matters. Default names to print tables w/email subject, timestamp, etc for. # TODO: get rid of this ?
|
|
46
|
+
DEFAULT_EMAILER_TABLES: list[str | None] = [
|
|
47
|
+
GHISLAINE_MAXWELL,
|
|
48
|
+
LEON_BLACK,
|
|
49
|
+
SULTAN_BIN_SULAYEM,
|
|
50
|
+
DEEPAK_CHOPRA,
|
|
51
|
+
ARIANE_DE_ROTHSCHILD,
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
if len(set(DEFAULT_EMAILERS).intersection(set(DEFAULT_EMAILER_TABLES))) > 0:
|
|
55
|
+
raise RuntimeError(f"Some names appear in both DEFAULT_EMAILERS and DEFAULT_EMAILER_TABLES")
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def print_emails(epstein_files: EpsteinFiles) -> int:
|
|
59
|
+
"""Returns number of emails printed."""
|
|
60
|
+
print_section_header(('Selections from ' if not args.all_emails else '') + 'His Emails')
|
|
61
|
+
print_other_site_link(is_header=False)
|
|
62
|
+
|
|
63
|
+
emailers_to_print: list[str | None]
|
|
64
|
+
emailer_tables: list[str | None] = []
|
|
65
|
+
already_printed_emails: list[Email] = []
|
|
66
|
+
num_emails_printed_since_last_color_key = 0
|
|
67
|
+
|
|
68
|
+
if specified_names:
|
|
69
|
+
emailers_to_print = specified_names
|
|
70
|
+
else:
|
|
71
|
+
epstein_files.print_emailer_counts_table()
|
|
72
|
+
|
|
73
|
+
if args.all_emails:
|
|
74
|
+
emailers_to_print = sorted(epstein_files.all_emailers(), key=lambda e: epstein_files.earliest_email_at(e))
|
|
75
|
+
console.print('Email conversations are sorted chronologically based on time of the first email.')
|
|
76
|
+
print_numbered_list_of_emailers(emailers_to_print, epstein_files)
|
|
77
|
+
else:
|
|
78
|
+
emailers_to_print = DEFAULT_EMAILERS
|
|
79
|
+
emailer_tables = DEFAULT_EMAILER_TABLES
|
|
80
|
+
console.print('Email conversations grouped by counterparty can be found in the order listed below.')
|
|
81
|
+
print_numbered_list_of_emailers(emailers_to_print)
|
|
82
|
+
console.print("\nAfter that there's tables linking to (but not displaying) all known emails for each of these people:")
|
|
83
|
+
print_numbered_list_of_emailers(emailer_tables)
|
|
84
|
+
|
|
85
|
+
for author in emailers_to_print:
|
|
86
|
+
author_emails = epstein_files.print_emails_for(author)
|
|
87
|
+
already_printed_emails.extend(author_emails)
|
|
88
|
+
num_emails_printed_since_last_color_key += len(author_emails)
|
|
89
|
+
|
|
90
|
+
# Print color key every once in a while
|
|
91
|
+
if num_emails_printed_since_last_color_key > PRINT_COLOR_KEY_EVERY_N_EMAILS:
|
|
92
|
+
print_color_key()
|
|
93
|
+
num_emails_printed_since_last_color_key = 0
|
|
94
|
+
|
|
95
|
+
if emailer_tables:
|
|
96
|
+
print_author_header(f"Email Tables for {len(emailer_tables)} Other People", 'white')
|
|
97
|
+
|
|
98
|
+
for name in DEFAULT_EMAILER_TABLES:
|
|
99
|
+
epstein_files.print_emails_table_for(name)
|
|
100
|
+
|
|
101
|
+
if not specified_names:
|
|
102
|
+
epstein_files.print_email_device_info()
|
|
103
|
+
|
|
104
|
+
if args.all_emails:
|
|
105
|
+
_verify_all_emails_printed(epstein_files, already_printed_emails)
|
|
106
|
+
|
|
107
|
+
logger.warning(f"Rewrote {len(Email.rewritten_header_ids)} headers of {len(epstein_files.emails)} emails")
|
|
108
|
+
return len(already_printed_emails)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def print_json_metadata(epstein_files: EpsteinFiles) -> None:
|
|
112
|
+
json_str = epstein_files.json_metadata()
|
|
113
|
+
|
|
114
|
+
if args.build:
|
|
115
|
+
with open(JSON_METADATA_PATH, 'w') as f:
|
|
116
|
+
f.write(json_str)
|
|
117
|
+
log_file_write(JSON_METADATA_PATH)
|
|
118
|
+
else:
|
|
119
|
+
console.print_json(json_str, indent=4, sort_keys=True)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def print_json_stats(epstein_files: EpsteinFiles) -> None:
|
|
123
|
+
console.line(5)
|
|
124
|
+
console.print(Panel('JSON Stats Dump', expand=True, style='reverse bold'), '\n')
|
|
125
|
+
print_json(f"{MESSENGER_LOG_CLASS} Sender Counts", MessengerLog.count_authors(epstein_files.imessage_logs), skip_falsey=True)
|
|
126
|
+
print_json(f"{EMAIL_CLASS} Author Counts", epstein_files.email_author_counts, skip_falsey=True)
|
|
127
|
+
print_json(f"{EMAIL_CLASS} Recipient Counts", epstein_files.email_recipient_counts, skip_falsey=True)
|
|
128
|
+
print_json("Email signature_substitution_countss", epstein_files.email_signature_substitution_counts(), skip_falsey=True)
|
|
129
|
+
print_json("email_author_device_signatures", dict_sets_to_lists(epstein_files.email_authors_to_device_signatures))
|
|
130
|
+
print_json("email_sent_from_devices", dict_sets_to_lists(epstein_files.email_device_signatures_to_authors))
|
|
131
|
+
print_json("email_unknown_recipient_file_ids", epstein_files.email_unknown_recipient_file_ids())
|
|
132
|
+
print_json("count_by_month", count_by_month(epstein_files.all_documents()))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def print_text_messages(epstein_files: EpsteinFiles) -> None:
|
|
136
|
+
print_section_header('Text Messages')
|
|
137
|
+
print_centered("(conversations are sorted chronologically based on timestamp of first message)\n", style='gray30')
|
|
138
|
+
authors: list[str | None] = specified_names if specified_names else [JEFFREY_EPSTEIN]
|
|
139
|
+
log_files = epstein_files.imessage_logs_for(authors)
|
|
140
|
+
|
|
141
|
+
for log_file in log_files:
|
|
142
|
+
console.print(Padding(log_file))
|
|
143
|
+
console.line(2)
|
|
144
|
+
|
|
145
|
+
epstein_files.print_imessage_summary()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def write_urls() -> None:
|
|
149
|
+
if args.output_file == 'index.html':
|
|
150
|
+
logger.warning(f"Can't write env vars to '{args.output_file}', writing to '{URLS_ENV}' instead.\n")
|
|
151
|
+
args.output_file = URLS_ENV
|
|
152
|
+
|
|
153
|
+
url_vars = {
|
|
154
|
+
k: v for k, v in vars(urls).items()
|
|
155
|
+
if isinstance(v, str) and \
|
|
156
|
+
k.split('_')[-1] in ['URL'] and \
|
|
157
|
+
'michelcrypt4d4mus' in v and \
|
|
158
|
+
'github.com' not in v and \
|
|
159
|
+
'BASE' not in v
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
with open(args.output_file, 'w') as f:
|
|
163
|
+
for var_name, url in url_vars.items():
|
|
164
|
+
key_value = f"{var_name}='{url}'"
|
|
165
|
+
console.print(key_value, style='dim')
|
|
166
|
+
f.write(f"{key_value}\n")
|
|
167
|
+
|
|
168
|
+
console.line()
|
|
169
|
+
logger.warning(f"Wrote {len(url_vars)} URL variables to '{args.output_file}'\n")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _verify_all_emails_printed(epstein_files: EpsteinFiles, already_printed_emails: list[Email]) -> None:
|
|
174
|
+
"""Log warnings if some emails were never printed."""
|
|
175
|
+
email_ids_that_were_printed = set([email.file_id for email in already_printed_emails])
|
|
176
|
+
logger.warning(f"Printed {len(already_printed_emails)} emails of {len(email_ids_that_were_printed)} unique file IDs.")
|
|
177
|
+
|
|
178
|
+
for email in epstein_files.emails:
|
|
179
|
+
if email.file_id not in email_ids_that_were_printed and not email.is_duplicate:
|
|
180
|
+
logger.warning(f"Failed to print {email.summary()}")
|
epstein_files/util/rich.py
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import json
|
|
3
3
|
from os import devnull
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Literal
|
|
6
5
|
|
|
7
6
|
from rich.align import Align
|
|
8
7
|
from rich.console import Console, RenderableType
|
|
@@ -18,9 +17,10 @@ from epstein_files.util.constant.names import UNKNOWN
|
|
|
18
17
|
from epstein_files.util.constant.strings import DEFAULT, EMAIL, NA, OTHER_SITE_LINK_STYLE, QUESTION_MARKS, SiteType
|
|
19
18
|
from epstein_files.util.constant.urls import *
|
|
20
19
|
from epstein_files.util.constants import FALLBACK_TIMESTAMP, HEADER_ABBREVIATIONS
|
|
21
|
-
from epstein_files.util.
|
|
22
|
-
from epstein_files.util.
|
|
23
|
-
from epstein_files.util.highlighted_group import
|
|
20
|
+
from epstein_files.util.data import json_safe
|
|
21
|
+
from epstein_files.util.env import args
|
|
22
|
+
from epstein_files.util.highlighted_group import ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter
|
|
23
|
+
from epstein_files.util.logging import log_file_write, logger
|
|
24
24
|
|
|
25
25
|
TITLE_WIDTH = 50
|
|
26
26
|
NUM_COLOR_KEY_COLS = 4
|
|
@@ -38,20 +38,19 @@ TITLE_STYLE = 'black on bright_white bold'
|
|
|
38
38
|
|
|
39
39
|
HIGHLIGHTED_GROUP_COLOR_KEYS = [
|
|
40
40
|
Text(highlight_group.label.replace('_', ' '), style=highlight_group.style)
|
|
41
|
-
for highlight_group in sorted(
|
|
42
|
-
if not highlight_group.is_multiline
|
|
41
|
+
for highlight_group in sorted(HIGHLIGHTED_NAMES, key=lambda hg: hg.label)
|
|
43
42
|
]
|
|
44
43
|
|
|
45
44
|
THEME_STYLES = {
|
|
46
45
|
DEFAULT: 'wheat4',
|
|
47
46
|
TEXT_LINK: 'deep_sky_blue4 underline',
|
|
48
|
-
**{hg.theme_style_name: hg.style for hg in
|
|
47
|
+
**{hg.theme_style_name: hg.style for hg in ALL_HIGHLIGHTS},
|
|
49
48
|
}
|
|
50
49
|
|
|
51
50
|
# Instantiate console object
|
|
52
51
|
CONSOLE_ARGS = {
|
|
53
52
|
'color_system': '256',
|
|
54
|
-
'highlighter':
|
|
53
|
+
'highlighter': EpsteinHighlighter(),
|
|
55
54
|
'record': args.build,
|
|
56
55
|
'safe_box': False,
|
|
57
56
|
'theme': Theme(THEME_STYLES),
|
|
@@ -72,6 +71,14 @@ def add_cols_to_table(table: Table, col_names: list[str]) -> None:
|
|
|
72
71
|
table.add_column(col, justify='left' if i == 0 else 'center')
|
|
73
72
|
|
|
74
73
|
|
|
74
|
+
def build_highlighter(pattern: str) -> EpsteinHighlighter:
|
|
75
|
+
class TempHighlighter(EpsteinHighlighter):
|
|
76
|
+
"""rich.highlighter that finds and colors interesting keywords based on the above config."""
|
|
77
|
+
highlights = EpsteinHighlighter.highlights + [re.compile(fr"(?P<trump>{pattern})", re.IGNORECASE)]
|
|
78
|
+
|
|
79
|
+
return TempHighlighter()
|
|
80
|
+
|
|
81
|
+
|
|
75
82
|
def join_texts(txts: list[Text], join: str = ' ', encloser: str = '') -> Text:
|
|
76
83
|
"""Join rich.Text objs into one."""
|
|
77
84
|
if encloser:
|
|
@@ -90,8 +97,11 @@ def join_texts(txts: list[Text], join: str = ' ', encloser: str = '') -> Text:
|
|
|
90
97
|
return txt
|
|
91
98
|
|
|
92
99
|
|
|
93
|
-
def key_value_txt(key: str, value: Text | str) -> Text:
|
|
100
|
+
def key_value_txt(key: str, value: Text | int | str) -> Text:
|
|
94
101
|
"""Generate a Text obj for 'key=value'."""
|
|
102
|
+
if isinstance(value, int):
|
|
103
|
+
value = Text(f"{value}", style='cyan')
|
|
104
|
+
|
|
95
105
|
return Text('').append(key, style=KEY_STYLE).append('=', style=SYMBOL_STYLE).append(value)
|
|
96
106
|
|
|
97
107
|
|
|
@@ -121,7 +131,7 @@ def print_centered_link(url: str, link_text: str, style: str | None = None) -> N
|
|
|
121
131
|
print_centered(link_markup(url, link_text, style or ARCHIVE_LINK_COLOR))
|
|
122
132
|
|
|
123
133
|
|
|
124
|
-
def print_color_key(
|
|
134
|
+
def print_color_key() -> None:
|
|
125
135
|
color_table = Table(title=f'Rough Guide to Highlighted Colors', show_header=False)
|
|
126
136
|
num_colors = len(HIGHLIGHTED_GROUP_COLOR_KEYS)
|
|
127
137
|
row_number = 0
|
|
@@ -153,13 +163,13 @@ def print_header(epstein_files: 'EpsteinFiles') -> None:
|
|
|
153
163
|
|
|
154
164
|
|
|
155
165
|
def print_json(label: str, obj: object, skip_falsey: bool = False) -> None:
|
|
166
|
+
print(obj)
|
|
167
|
+
|
|
156
168
|
if isinstance(obj, dict):
|
|
157
169
|
if skip_falsey:
|
|
158
170
|
obj = {k: v for k, v in obj.items() if v}
|
|
159
171
|
|
|
160
|
-
|
|
161
|
-
obj = {k or UNKNOWN: v for k, v in obj.items()}
|
|
162
|
-
|
|
172
|
+
obj = json_safe(obj)
|
|
163
173
|
|
|
164
174
|
console.line()
|
|
165
175
|
console.print(Panel(label, expand=False))
|
|
@@ -218,6 +228,8 @@ def print_other_site_link(is_header: bool = True) -> None:
|
|
|
218
228
|
print_centered(parenthesize(Text.from_markup(markup_msg)), style='bold')
|
|
219
229
|
word_count_link = link_text_obj(WORD_COUNT_URL, 'site showing the most frequently used words in these communiques', OTHER_SITE_LINK_STYLE)
|
|
220
230
|
print_centered(parenthesize(word_count_link))
|
|
231
|
+
metadata_link = link_text_obj(JSON_METADATA_URL, 'metadata with author attribution explanations', OTHER_SITE_LINK_STYLE)
|
|
232
|
+
print_centered(parenthesize(metadata_link))
|
|
221
233
|
|
|
222
234
|
|
|
223
235
|
def print_page_title(expand: bool = True, width: int | None = None) -> None:
|
|
@@ -292,8 +304,8 @@ def write_html(output_path: Path) -> None:
|
|
|
292
304
|
logger.warning(f"Not writing HTML because args.build={args.build}.")
|
|
293
305
|
return
|
|
294
306
|
|
|
295
|
-
console.save_html(output_path, code_format=CONSOLE_HTML_FORMAT, theme=HTML_TERMINAL_THEME)
|
|
296
|
-
|
|
307
|
+
console.save_html(str(output_path), code_format=CONSOLE_HTML_FORMAT, theme=HTML_TERMINAL_THEME)
|
|
308
|
+
log_file_write(output_path)
|
|
297
309
|
|
|
298
310
|
|
|
299
311
|
def _print_abbreviations_table() -> None:
|
|
@@ -320,5 +332,5 @@ def _print_external_links() -> None:
|
|
|
320
332
|
print_centered(link_markup(EPSTEIN_WEB_URL) + " (character summaries)")
|
|
321
333
|
|
|
322
334
|
|
|
323
|
-
if args.deep_debug:
|
|
324
|
-
|
|
335
|
+
# if args.deep_debug:
|
|
336
|
+
# print_json('THEME_STYLES', THEME_STYLES)
|
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
3
|
from rich.text import Text
|
|
4
|
+
# from epstein_files.documents.document import type Document
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class MatchedLine:
|
|
9
|
+
line: str
|
|
10
|
+
line_number: int
|
|
11
|
+
|
|
12
|
+
def __rich__(self) -> Text:
|
|
13
|
+
return Text('').append(str(self.line_number), style='cyan').append(f":{self.line}")
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return f"{self.line_number}:{self.line}"
|
|
6
17
|
|
|
7
18
|
|
|
8
19
|
@dataclass
|
|
9
20
|
class SearchResult:
|
|
10
21
|
"""Simple class used for collecting documents that match a given search term."""
|
|
11
|
-
document: Document
|
|
12
|
-
lines: list[
|
|
13
|
-
|
|
14
|
-
def unprefixed_lines(self) -> list[str]:
|
|
15
|
-
return [line.plain.split(':', 1)[1] for line in self.lines]
|
|
22
|
+
document: 'Document'
|
|
23
|
+
lines: list[MatchedLine]
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
|
|
4
|
+
from epstein_files.util.logging import logger
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Timer:
|
|
9
|
+
started_at: float = field(default_factory=lambda: time.perf_counter())
|
|
10
|
+
checkpoint_at: float = field(default_factory=lambda: time.perf_counter())
|
|
11
|
+
decimals: int = 2
|
|
12
|
+
|
|
13
|
+
def print_at_checkpoint(self, msg: str) -> None:
|
|
14
|
+
logger.warning(f"{msg} in {self.seconds_since_checkpoint_str()}")
|
|
15
|
+
self.checkpoint_at = time.perf_counter()
|
|
16
|
+
|
|
17
|
+
def seconds_since_checkpoint_str(self) -> str:
|
|
18
|
+
return f"{(time.perf_counter() - self.checkpoint_at):.{self.decimals}f} seconds"
|
|
19
|
+
|
|
20
|
+
def seconds_since_start(self) -> float:
|
|
21
|
+
return time.perf_counter() - self.started_at
|
|
22
|
+
|
|
23
|
+
def seconds_since_start_str(self) -> str:
|
|
24
|
+
return f"{self.seconds_since_start():.{self.decimals}f} seconds"
|
epstein_files/util/word_count.py
CHANGED
|
@@ -12,7 +12,8 @@ from epstein_files.documents.emails.email_header import EmailHeader
|
|
|
12
12
|
from epstein_files.util.constant.common_words import COMMON_WORDS, UNSINGULARIZABLE_WORDS
|
|
13
13
|
from epstein_files.util.constant.names import OTHER_NAMES
|
|
14
14
|
from epstein_files.util.data import ALL_NAMES, flatten, sort_dict
|
|
15
|
-
from epstein_files.util.env import args
|
|
15
|
+
from epstein_files.util.env import args
|
|
16
|
+
from epstein_files.util.logging import logger
|
|
16
17
|
from epstein_files.util.rich import highlighter
|
|
17
18
|
from epstein_files.util.search_result import SearchResult
|
|
18
19
|
|
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: epstein-files
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.2
|
|
4
4
|
Summary: Tools for working with the Jeffrey Epstein documents released in November 2025.
|
|
5
|
+
Home-page: https://michelcrypt4d4mus.github.io/epstein_text_messages/
|
|
6
|
+
License: GPL-3.0-or-later
|
|
7
|
+
Keywords: Epstein,Jeffrey Epstein
|
|
5
8
|
Author: Michel de Cryptadamus
|
|
6
9
|
Requires-Python: >=3.11,<4.0
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Information Technology
|
|
13
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
7
15
|
Classifier: Programming Language :: Python :: 3
|
|
8
16
|
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
9
20
|
Requires-Dist: datefinder (>=0.7.3,<0.8.0)
|
|
10
21
|
Requires-Dist: inflection (>=0.5.1,<0.6.0)
|
|
11
22
|
Requires-Dist: python-dateutil (>=2.9.0.post0,<3.0.0)
|
|
12
23
|
Requires-Dist: python-dotenv (>=1.2.1,<2.0.0)
|
|
13
24
|
Requires-Dist: requests (>=2.32.5,<3.0.0)
|
|
14
25
|
Requires-Dist: rich (>=14.2.0,<15.0.0)
|
|
26
|
+
Project-URL: Emails, https://michelcrypt4d4mus.github.io/epstein_text_messages/all_emails_epstein_files_nov_2025.html
|
|
27
|
+
Project-URL: Metadata, https://michelcrypt4d4mus.github.io/epstein_text_messages/file_metadata_epstein_files_nov_2025.json
|
|
28
|
+
Project-URL: TextMessages, https://michelcrypt4d4mus.github.io/epstein_text_messages
|
|
29
|
+
Project-URL: WordCounts, https://michelcrypt4d4mus.github.io/epstein_text_messages/communication_word_count_epstein_files_nov_2025.html
|
|
15
30
|
Description-Content-Type: text/markdown
|
|
16
31
|
|
|
17
32
|
# I Made Epstein's Text Messages Great Again
|
|
18
33
|
|
|
19
34
|
* [I Made Epstein's Text Messages Great Again (And You Should Read Them)](https://cryptadamus.substack.com/p/i-made-epsteins-text-messages-great) post on [Substack](https://cryptadamus.substack.com/p/i-made-epsteins-text-messages-great)
|
|
20
35
|
* The Epstein text messages (and some of the emails along with summary counts of sent emails to/from Epstein) generated by this code can be viewed [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/).
|
|
21
|
-
* All of His Emails can be read at another page also generated by this code [here](https://michelcrypt4d4mus.github.io/
|
|
22
|
-
* Word counts for the emails and text messages are [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/
|
|
23
|
-
*
|
|
36
|
+
* All of His Emails can be read at another page also generated by this code [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/all_emails_epstein_files_nov_2025.html).
|
|
37
|
+
* Word counts for the emails and text messages are [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/communication_word_count_epstein_files_nov_2025.html).
|
|
38
|
+
* Metadata containing what I have figured out about who sent or received the communications in a given file (and a brief explanation for how I figured it out for each file) is deployed [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/file_metadata_epstein_files_nov_2025.json)
|
|
39
|
+
* Configuration variables assigning specific `HOUSE_OVERSIGHT_XXXXXX.txt` file IDs (the `111111` part) as being emails to or from particular people based on various research and contributions can be found in [constants.py](./epstein_files/util/constants.py). Everything in `constants.py` should also appear in the JSON metadata.
|
|
24
40
|
|
|
25
41
|
|
|
26
42
|
### Usage
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
epstein_files/__init__.py,sha256=N4-A81KlSNWXyliBsjooi6GZr9_2qIB08qTG5RE9GzA,4725
|
|
2
|
+
epstein_files/documents/communication.py,sha256=SunZdjMhR9v6y8LlQ6jhIu8vYjSndaBK0Su1mKnhfj0,2060
|
|
3
|
+
epstein_files/documents/document.py,sha256=MOTS6AZFNOqnzpvYLXvoJC05ZVL9lTZnsRc-qjAjcJ4,16515
|
|
4
|
+
epstein_files/documents/email.py,sha256=IpKeOuLTmHWowBvUUEp-tyTC8pwEmXg3XgLJkplQZWg,37717
|
|
5
|
+
epstein_files/documents/emails/email_header.py,sha256=wkPfSLbmzkAeQwvhf0bAeFDLPbQT-EeG0v8vNNLYktM,7502
|
|
6
|
+
epstein_files/documents/imessage/text_message.py,sha256=DMdd__L7UPad0YS49MJf_3bTVyE1BLfWafQbDQierC8,3272
|
|
7
|
+
epstein_files/documents/json_file.py,sha256=Vf9iHiEhUqk-hEh6lGZX_mJNWZUUQWeT-UI3FhKW0hw,1101
|
|
8
|
+
epstein_files/documents/messenger_log.py,sha256=-hSMFH_dyrjGLdph4SU2xQK4RpXOhkR3R_nqRrz3e1w,5620
|
|
9
|
+
epstein_files/documents/other_file.py,sha256=JbKDtNrQ1Ua5vGPlZayON5Kgy0oJ-gHcdO9l9Iv9wRo,8930
|
|
10
|
+
epstein_files/epstein_files.py,sha256=NpgQaxM3cC8CsAbzCyysakMbPdASWAt-wOhPZ879ZyQ,18018
|
|
11
|
+
epstein_files/util/constant/common_words.py,sha256=aR0UjoWmxyR49XS-DtHECQ1CiA_bK8hNP6CQ1TS9yZA,3696
|
|
12
|
+
epstein_files/util/constant/html.py,sha256=9U098TGzlghGg4WfxLYHyub5JGR17Dv7VP5i2MSu8Kk,1415
|
|
13
|
+
epstein_files/util/constant/names.py,sha256=g0s9NA4zIexDGG7u5x0KDpLB9fyrYaYatsBhf_lH688,10253
|
|
14
|
+
epstein_files/util/constant/output_files.py,sha256=B2qEXfNI_gT2Vp5HGSld2xM0PfeZ27j65HNymSmyzX4,974
|
|
15
|
+
epstein_files/util/constant/strings.py,sha256=3JTqD0luJrC3NbGXn4q6P-gIaaNVx36P1oCmp92gAoM,1750
|
|
16
|
+
epstein_files/util/constant/urls.py,sha256=x7Lv8yNNNLGU3GCvG4YbJ4qX3s_hiXffYuYUCjXyfbg,5526
|
|
17
|
+
epstein_files/util/constants.py,sha256=xME94iH9a4b95N4yABs20Gn3Tu3cwmx5kNE_fPhsJEM,103420
|
|
18
|
+
epstein_files/util/data.py,sha256=P4D_ggNNyScpTnu9wow8-67BlZtAXFKulJ5zbGtBR9A,2907
|
|
19
|
+
epstein_files/util/doc_cfg.py,sha256=6H5EFLxG0ABG4BJHIEL7PSMBVwkcyjH1vvmitSQRa48,9615
|
|
20
|
+
epstein_files/util/env.py,sha256=A2hEVg1HYymGd2odrLLo6k7yIvu0hh1XZniUW2u21dM,4734
|
|
21
|
+
epstein_files/util/file_helper.py,sha256=v_bE10MHEcXti9DVJo4WqyOsG83Xrv05S3Vc70cYJkk,3082
|
|
22
|
+
epstein_files/util/highlighted_group.py,sha256=7MfES52q10eq35L93iTIr_v5v0bamdQKCf7hlDIy7O8,35196
|
|
23
|
+
epstein_files/util/logging.py,sha256=GjmOYiWAF1R_0Dvb5kXHAgPH5UJs-_gGcRig7LEDDL0,2066
|
|
24
|
+
epstein_files/util/output.py,sha256=YH_-1YyNxiO29N88jvSAePbK5n25tgEIDFyP3sNNnnI,7309
|
|
25
|
+
epstein_files/util/rich.py,sha256=gvSbWc-PQC5cAUg8zn02yZQkU57ExG1ArRLMIPrVxOc,13597
|
|
26
|
+
epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
|
|
27
|
+
epstein_files/util/timer.py,sha256=8hxW4Y1JcTUfnBrHh7sL2pM9xu1sL4HFQM4CmmzTarU,837
|
|
28
|
+
epstein_files/util/word_count.py,sha256=XTINgLm01jFQlNgdiLCcVFCodXAIb1dNbaAvznoRb1o,6757
|
|
29
|
+
epstein_files-1.0.2.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
30
|
+
epstein_files-1.0.2.dist-info/METADATA,sha256=Wf4rPDzdGRxogbs0H8cZoFb7HsRTrkPvkYHdClbg74c,4653
|
|
31
|
+
epstein_files-1.0.2.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
32
|
+
epstein_files-1.0.2.dist-info/entry_points.txt,sha256=EV9qTh_n9X_1MOiQnxG5hM6R5v0rfi8W4QE-bsZkw3o,238
|
|
33
|
+
epstein_files-1.0.2.dist-info/RECORD,,
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
epstein_files/__init__.py,sha256=JlGEliI8H5TqdGuCTtGIwLiM7GutbyND-IURG5Z-gAA,7442
|
|
2
|
-
epstein_files/documents/communication.py,sha256=XybnJDqyK00RTeEkbLWgrVDkZS7pmC8EoftX1_G4OIM,2066
|
|
3
|
-
epstein_files/documents/document.py,sha256=6av_KLTm70KIABl9Fl7DL9jKkvqdN18OLmECj2PAkEs,15223
|
|
4
|
-
epstein_files/documents/email.py,sha256=rzgyVMBlKo9ytRdE6Eif_8zF4fecaMduIgPQkvRmmk0,34283
|
|
5
|
-
epstein_files/documents/emails/email_header.py,sha256=JqcSqa-amdombgszE_cjXBrvZKE0HxU2mNAoD44Ekh4,7479
|
|
6
|
-
epstein_files/documents/imessage/text_message.py,sha256=zU46FHeHKG6vbIZ-qLDdZC-1f3VmXW73fKo-BKOCgq8,3199
|
|
7
|
-
epstein_files/documents/json_file.py,sha256=sL99E_34X9BIwQ27vE0Afy446oiyCmPLYdsJ6-OGMHU,611
|
|
8
|
-
epstein_files/documents/messenger_log.py,sha256=HbtqthtkzXhkY7DxwxFOBy6xx6gPmmXIxZ4bsloLHEg,2995
|
|
9
|
-
epstein_files/documents/other_file.py,sha256=_Z8EVLLy1u8RuEr5iPTKJdYZjQHFHp0o-mBErBYsRXg,4699
|
|
10
|
-
epstein_files/epstein_files.py,sha256=Fb-SPwRlHZPB7iL48dL3Da4nPyzF9_Rk5nNpVnfku2U,20131
|
|
11
|
-
epstein_files/util/constant/common_words.py,sha256=aR0UjoWmxyR49XS-DtHECQ1CiA_bK8hNP6CQ1TS9yZA,3696
|
|
12
|
-
epstein_files/util/constant/html.py,sha256=9U098TGzlghGg4WfxLYHyub5JGR17Dv7VP5i2MSu8Kk,1415
|
|
13
|
-
epstein_files/util/constant/names.py,sha256=_iNHJsr4sdUXG1cozllRy8yV6SYXgsr6oTPHTCbrpj0,9967
|
|
14
|
-
epstein_files/util/constant/strings.py,sha256=B5RpaGCc1EqiDKF-3BabNy4dJU6n4kXytx96aanHIDA,1115
|
|
15
|
-
epstein_files/util/constant/urls.py,sha256=pks5frfWXmFwEkfB33Uy7IR1mn0DaAI_8kDjA_g03yg,5089
|
|
16
|
-
epstein_files/util/constants.py,sha256=jErrj2r_K8pbFNih7WxlcP0sPO7ZyzrLxH3MlSw5xR4,106137
|
|
17
|
-
epstein_files/util/data.py,sha256=7yz3osIlpjV9nbPEvRBmExokBsgm20kitSfqNpaYWj4,3746
|
|
18
|
-
epstein_files/util/env.py,sha256=T-sd1lPfw-L6WXvpIv-cqGTqBfZvacZvniW1OKOdkrU,4624
|
|
19
|
-
epstein_files/util/file_cfg.py,sha256=E7VVxVZ_cSN18HgI6q8e6UwTApMWuPC9qTqb3_tXCqs,7183
|
|
20
|
-
epstein_files/util/file_helper.py,sha256=EHVRFxcWmc0RD1VUeLFCcmM-82c5FISIUBIwg4TodsY,2727
|
|
21
|
-
epstein_files/util/highlighted_group.py,sha256=0MpzBYVaGfyUZQwFP7EVrLHDYU57qnnmNDC56gVxq2Q,33091
|
|
22
|
-
epstein_files/util/rich.py,sha256=Z28-etKpZdc6aPxdmAxNe9ZvMWK28iubYASt50VxtJ4,13194
|
|
23
|
-
epstein_files/util/search_result.py,sha256=5nkR-YzUiR52FmGIad_Uw0XaU2E_FfHZBHgc8dm3sAs,429
|
|
24
|
-
epstein_files/util/word_count.py,sha256=L1T6zjShaPZo1ilpM0dSAT3X-EgaZ4YEigHNLxjKit4,6719
|
|
25
|
-
epstein_files-1.0.0.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
26
|
-
epstein_files-1.0.0.dist-info/METADATA,sha256=dGc412CXHc2EHOd0C7HuEa2qFQFh42dNbDw5CiH0Ny4,3239
|
|
27
|
-
epstein_files-1.0.0.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
|
|
28
|
-
epstein_files-1.0.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|