epstein-files 1.0.1__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.
@@ -3,15 +3,10 @@ Helpers for dealing with various kinds of data.
3
3
  """
4
4
  import itertools
5
5
  import re
6
- import time
7
- from dataclasses import dataclass, field
8
6
  from datetime import datetime, timezone
9
7
  from dateutil import tz
10
8
  from typing import TypeVar
11
9
 
12
- from dateutil.parser import parse
13
- from rich.text import Text
14
-
15
10
  from epstein_files.util.constant import names
16
11
  from epstein_files.util.env import args
17
12
  from epstein_files.util.logging import logger
@@ -24,27 +19,20 @@ CONSTANT_VAR_REGEX = re.compile(r"^[A-Z_]+$")
24
19
  ALL_NAMES = [v for k, v in vars(names).items() if isinstance(v, str) and CONSTANT_VAR_REGEX.match(k)]
25
20
 
26
21
  PACIFIC_TZ = tz.gettz("America/Los_Angeles")
27
- TIMEZONE_INFO = {"PST": PACIFIC_TZ, "PDT": PACIFIC_TZ} # Suppresses annoying warnings from parse() calls
28
-
29
-
30
- def dict_sets_to_lists(d: dict[str, set]) -> dict[str, list]:
31
- return {k: sorted(list(v)) for k, v in d.items()}
32
-
33
-
34
- def extract_datetime(s: str) -> datetime | None:
35
- match = ISO_DATE_REGEX.search(s)
22
+ TIMEZONE_INFO = {"PDT": PACIFIC_TZ, "PST": PACIFIC_TZ} # Suppresses annoying warnings from parse() calls
36
23
 
37
- if not match:
38
- return None
39
24
 
40
- date_str = match.group(0)
25
+ collapse_newlines = lambda text: MULTINEWLINE_REGEX.sub('\n\n', text)
26
+ date_str = lambda dt: dt.isoformat()[0:10] if dt else None
27
+ escape_double_quotes = lambda text: text.replace('"', r'\"')
28
+ escape_single_quotes = lambda text: text.replace("'", r"\'")
29
+ iso_timestamp = lambda dt: dt.isoformat().replace('T', ' ')
30
+ uniquify = lambda _list: list(set(_list))
31
+ without_nones = lambda _list: [e for e in _list if e]
41
32
 
42
- if len(date_str) == 4:
43
- date_str += '-01-01'
44
- elif len(date_str) == 7:
45
- date_str += '-01'
46
33
 
47
- return parse(date_str, tzinfos=TIMEZONE_INFO)
34
+ def dict_sets_to_lists(d: dict[str, set]) -> dict[str, list]:
35
+ return {k: sorted(list(v)) for k, v in d.items()}
48
36
 
49
37
 
50
38
  def extract_last_name(name: str) -> str:
@@ -91,8 +79,8 @@ def ordinal_str(n: int) -> str:
91
79
  return str(n) + suffix
92
80
 
93
81
 
94
- def patternize(_pattern: str | re.Pattern):
95
- return _pattern if isinstance(_pattern, re.Pattern) else re.compile(rf"({_pattern})", re.IGNORECASE)
82
+ def patternize(_pattern: str | re.Pattern) -> re.Pattern:
83
+ return _pattern if isinstance(_pattern, re.Pattern) else re.compile(fr"({_pattern})", re.IGNORECASE)
96
84
 
97
85
 
98
86
  def remove_timezone(timestamp: datetime) -> datetime:
@@ -106,12 +94,3 @@ def remove_timezone(timestamp: datetime) -> datetime:
106
94
  def sort_dict(d: dict[str | None, int] | dict[str, int]) -> list[tuple[str | None, int]]:
107
95
  sort_key = lambda e: (e[0] or '').lower() if args.sort_alphabetical else [-e[1], (e[0] or '').lower()]
108
96
  return sorted(d.items(), key=sort_key)
109
-
110
-
111
- collapse_newlines = lambda text: MULTINEWLINE_REGEX.sub('\n\n', text)
112
- date_str = lambda dt: dt.isoformat()[0:10] if dt else None
113
- escape_double_quotes = lambda text: text.replace('"', r'\"')
114
- escape_single_quotes = lambda text: text.replace("'", r"\'")
115
- iso_timestamp = lambda dt: dt.isoformat().replace('T', ' ')
116
- uniquify = lambda _list: list(set(_list))
117
- without_nones = lambda _list: [e for e in _list if e]
@@ -116,6 +116,8 @@ class DocCfg:
116
116
  return self.title_by_author()
117
117
  elif self.category == FINANCE and self.author in FINANCIAL_REPORTS_AUTHORS:
118
118
  return f"{self.author} report: '{self.description}'"
119
+ elif self.category == LEGAL and 'v.' in self.author:
120
+ return f"{self.author}: '{self.description}'"
119
121
  elif self.category and self.author is None and self.description is None:
120
122
  return self.category
121
123
 
@@ -176,16 +178,6 @@ class DocCfg:
176
178
 
177
179
  return props
178
180
 
179
- def __eq__(self, other: 'DocCfg') -> bool:
180
- """Return True if everything matches other than the two 'dupe_' fields ('duplicate_ids' is compared)."""
181
- for _field in self.sorted_fields():
182
- if _field.name == 'id' or _field.name.startswith('dupe'):
183
- continue
184
- elif getattr(self, _field.name) != getattr(other, _field.name):
185
- return False
186
-
187
- return True
188
-
189
181
  def __repr__(self) -> str:
190
182
  props = self._props_strs()
191
183
  type_str = f"{type(self).__name__}("
epstein_files/util/env.py CHANGED
@@ -7,15 +7,16 @@ from sys import argv
7
7
  from epstein_files.util.logging import datefinder_logger, env_log_level, logger
8
8
 
9
9
  DEFAULT_WIDTH = 154
10
- HTML_SCRIPTS = ['generate_html.py', 'count_words.py']
10
+ HTML_SCRIPTS = ['epstein_generate', 'generate_html.py', 'count_words.py']
11
11
 
12
12
 
13
13
  parser = ArgumentParser(description="Parse epstein OCR docs and generate HTML page.")
14
- parser.add_argument('--build', '-b', action='store_true', help='write HTML to docs/index.html')
14
+ parser.add_argument('--build', '-b', action='store_true', help='write output to file')
15
15
  parser.add_argument('--all-emails', '-ae', action='store_true', help='all the emails instead of just the interesting ones')
16
16
  parser.add_argument('--all-other-files', '-ao', action='store_true', help='all the non-email, non-text msg files instead of just interesting ones')
17
17
  parser.add_argument('--colors-only', '-c', action='store_true', help='print header with color key table and links and exit')
18
18
  parser.add_argument('--name', '-n', action='append', dest='names', help='specify the name(s) whose communications should be output')
19
+ parser.add_argument('--output-file', '-out', metavar='FILE', default='index.html', help='write output to FILE in docs/ (default=index.html)')
19
20
  parser.add_argument('--output-emails', '-oe', action='store_true', help='generate other files section')
20
21
  parser.add_argument('--output-other-files', '-oo', action='store_true', help='generate other files section')
21
22
  parser.add_argument('--output-texts', '-ot', action='store_true', help='generate other files section')
@@ -8,7 +8,6 @@ from epstein_files.util.constant.strings import FILE_NAME_REGEX, FILE_STEM_REGEX
8
8
  EPSTEIN_DOCS_DIR_ENV_VAR_NAME = 'EPSTEIN_DOCS_DIR'
9
9
  DOCS_DIR_ENV = environ[EPSTEIN_DOCS_DIR_ENV_VAR_NAME]
10
10
  DOCS_DIR = Path(DOCS_DIR_ENV or '').resolve()
11
- PICKLED_PATH = Path("the_epstein_files.pkl.gz")
12
11
 
13
12
  if not DOCS_DIR_ENV:
14
13
  print(f"ERROR: {EPSTEIN_DOCS_DIR_ENV_VAR_NAME} env var not set!")
@@ -17,20 +16,7 @@ elif not DOCS_DIR.exists():
17
16
  print(f"ERROR: {EPSTEIN_DOCS_DIR_ENV_VAR_NAME}='{DOCS_DIR}' does not exist!")
18
17
  exit(1)
19
18
 
20
- HTML_DIR = Path('docs')
21
19
  EXTRACTED_EMAILS_DIR = Path('emails_extracted_from_legal_filings')
22
- EPSTEIN_WORD_COUNT_HTML_PATH = HTML_DIR.joinpath('epstein_texts_and_emails_word_count.html')
23
- GH_PAGES_HTML_PATH = HTML_DIR.joinpath('index.html')
24
- JSON_METADATA_PATH = HTML_DIR.joinpath('epstein_files_nov_2025_cryptadamus_metadata.json')
25
- WORD_COUNT_HTML_PATH = HTML_DIR.joinpath('epstein_emails_word_count.html')
26
-
27
- BUILD_ARTIFACTS = [
28
- EPSTEIN_WORD_COUNT_HTML_PATH,
29
- GH_PAGES_HTML_PATH,
30
- JSON_METADATA_PATH,
31
- WORD_COUNT_HTML_PATH,
32
- ]
33
-
34
20
  FILE_ID_REGEX = re.compile(fr".*{FILE_NAME_REGEX.pattern}")
35
21
  FILENAME_LENGTH = len(HOUSE_OVERSIGHT_PREFIX) + 6
36
22
  KB = 1024
@@ -110,11 +96,3 @@ def is_local_extract_file(filename) -> bool:
110
96
  """Return true if filename is of form 'HOUSE_OVERSIGHT_029835_1.txt'."""
111
97
  file_match = FILE_ID_REGEX.match(str(filename))
112
98
  return True if file_match and file_match.group(2) else False
113
-
114
-
115
- def make_clean() -> None:
116
- """Delete all build artifacts."""
117
- for build_file in BUILD_ARTIFACTS:
118
- if build_file.exists():
119
- print(f"Removing build file '{build_file}'...")
120
- build_file.unlink()
@@ -272,7 +272,7 @@ HIGHLIGHTED_NAMES = [
272
272
  HighlightedNames(
273
273
  label='europe',
274
274
  style='light_sky_blue3',
275
- pattern=r'(Angela )?Merk(el|le)|Austria|(Benjamin\s*)?Harnwell|Berlin|Brexit(eers?)?|Brit(ain|ish)|Brussels|Cannes|(Caroline|Jack)?\s*Lang(, Caroline)?|Cypr(iot|us)|Davos|ECB|EU|Europe(an)?(\s*Union)?|France|Geneva|Germany?|Gillard|Gree(ce|k)|Ital(ian|y)|Jacques|(Kevin\s*)?Rudd|Le\s*Pen|London|Macron|Melusine|Munich|(Natalia\s*)?Veselnitskaya|(Nicholas\s*)?Sarkozy|Nigel(\s*Farage)?|Oslo|Paris|Polish|(Sebastian )?Kurz|(Vi(c|k)tor\s+)?Orbah?n|Edward Rod Larsen|Strasbourg|Strauss[- ]?Kahn|Swed(en|ish)(?![-\s]+America)|Switzerland|(Tony\s)?Blair|Ukrain(e|ian)|Vienna|(Vitaly\s*)?Churkin|Zug',
275
+ pattern=r'(Angela )?Merk(el|le)|Austria|(Benjamin\s*)?Harnwell|Berlin|Borge|Brexit(eers?)?|Brit(ain|ish)|Brussels|Cannes|(Caroline|Jack)?\s*Lang(, Caroline)?|Cypr(iot|us)|Davos|ECB|EU|Europe(an)?(\s*Union)?|France|Geneva|Germany?|Gillard|Gree(ce|k)|Ital(ian|y)|Jacques|(Kevin\s*)?Rudd|Le\s*Pen|London|Macron|Melusine|Munich|(Natalia\s*)?Veselnitskaya|(Nicholas\s*)?Sarkozy|Nigel(\s*Farage)?|Oslo|Paris|Polish|(Sebastian )?Kurz|(Vi(c|k)tor\s+)?Orbah?n|Edward Rod Larsen|Strasbourg|Strauss[- ]?Kahn|Swed(en|ish)(?![-\s]+America)|Switzerland|(Tony\s)?Blair|Ukrain(e|ian)|Vienna|(Vitaly\s*)?Churkin|Zug',
276
276
  emailers = {
277
277
  ANDRZEJ_DUDA: 'former president of Poland',
278
278
  MIROSLAV_LAJCAK: 'Russia-friendly Slovakian politician, friend of Steve Bannon',
@@ -519,7 +519,7 @@ HIGHLIGHTED_NAMES = [
519
519
  HighlightedNames(
520
520
  label='trump',
521
521
  style='red3 bold',
522
- pattern=r"@?realDonaldTrump|(Alan\s*)?Weiss?elberg|\bDJ?T\b|Donald J. Tramp|(Donald\s+(J\.\s+)?)?Trump(ism|\s*Properties)?|Don(ald| *Jr)(?! Rubin)|Ivana|(Madeleine\s*)?Westerhout|Mar[-\s]*a[-\s]*Lago|(Marla\s*)?Maples|(Matt(hew)? )?Calamari|\bMatt C\b|Melania|(Michael (J.? )?)?Boccio|Roger\s+Stone|rona|(The\s*)?Art\s*of\s*the\s*Deal",
522
+ pattern=r"@?realDonaldTrump|(Alan\s*)?Weiss?elberg|\bDJ?T\b|Donald J. Tramp|(Donald\s+(J\.\s+)?)?Trump(ism|\s*Properties)?|Don(ald| *Jr)(?! Rubin)|Ivana|(Madeleine\s*)?Westerhout|Mar[-\s]*a[-\s]*Lago|(Marla\s*)?Maples|(Matt(hew)? )?Calamari|\bMatt C\b|Melania|(Michael (J.? )?)?Boccio|Rebekah\s*Mercer|Roger\s+Stone|rona|(The\s*)?Art\s*of\s*the\s*Deal",
523
523
  emailers = {
524
524
  'Bruce Moskowitz': "'Trump's health guy' according to Epstein",
525
525
  },
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  from os import environ
3
+ from pathlib import Path
3
4
 
4
5
  from rich.console import Console
5
6
  from rich.highlighter import ReprHighlighter
@@ -7,6 +8,7 @@ from rich.logging import RichHandler
7
8
  from rich.theme import Theme
8
9
 
9
10
  from epstein_files.util.constant.strings import *
11
+ from epstein_files.util.file_helper import file_size_str
10
12
 
11
13
  FILENAME_STYLE = 'gray27'
12
14
 
@@ -55,3 +57,7 @@ if env_log_level_str:
55
57
  logger.warning(f"Setting log level to {env_log_level} based on {LOG_LEVEL_ENV_VAR} env var...")
56
58
  logger.setLevel(env_log_level)
57
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()}")
@@ -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
@@ -20,9 +19,8 @@ from epstein_files.util.constant.urls import *
20
19
  from epstein_files.util.constants import FALLBACK_TIMESTAMP, HEADER_ABBREVIATIONS
21
20
  from epstein_files.util.data import json_safe
22
21
  from epstein_files.util.env import args
23
- from epstein_files.util.file_helper import file_size_str
24
22
  from epstein_files.util.highlighted_group import ALL_HIGHLIGHTS, HIGHLIGHTED_NAMES, EpsteinHighlighter
25
- from epstein_files.util.logging import logger
23
+ from epstein_files.util.logging import log_file_write, logger
26
24
 
27
25
  TITLE_WIDTH = 50
28
26
  NUM_COLOR_KEY_COLS = 4
@@ -73,6 +71,14 @@ def add_cols_to_table(table: Table, col_names: list[str]) -> None:
73
71
  table.add_column(col, justify='left' if i == 0 else 'center')
74
72
 
75
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
+
76
82
  def join_texts(txts: list[Text], join: str = ' ', encloser: str = '') -> Text:
77
83
  """Join rich.Text objs into one."""
78
84
  if encloser:
@@ -91,8 +97,11 @@ def join_texts(txts: list[Text], join: str = ' ', encloser: str = '') -> Text:
91
97
  return txt
92
98
 
93
99
 
94
- def key_value_txt(key: str, value: Text | str) -> Text:
100
+ def key_value_txt(key: str, value: Text | int | str) -> Text:
95
101
  """Generate a Text obj for 'key=value'."""
102
+ if isinstance(value, int):
103
+ value = Text(f"{value}", style='cyan')
104
+
96
105
  return Text('').append(key, style=KEY_STYLE).append('=', style=SYMBOL_STYLE).append(value)
97
106
 
98
107
 
@@ -219,10 +228,8 @@ def print_other_site_link(is_header: bool = True) -> None:
219
228
  print_centered(parenthesize(Text.from_markup(markup_msg)), style='bold')
220
229
  word_count_link = link_text_obj(WORD_COUNT_URL, 'site showing the most frequently used words in these communiques', OTHER_SITE_LINK_STYLE)
221
230
  print_centered(parenthesize(word_count_link))
222
-
223
- if is_header:
224
- metadata_link = link_text_obj(JSON_METADATA_URL, 'file metadata containing author attribution explanations', OTHER_SITE_LINK_STYLE)
225
- print_centered(parenthesize(metadata_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))
226
233
 
227
234
 
228
235
  def print_page_title(expand: bool = True, width: int | None = None) -> None:
@@ -298,7 +305,7 @@ def write_html(output_path: Path) -> None:
298
305
  return
299
306
 
300
307
  console.save_html(str(output_path), code_format=CONSOLE_HTML_FORMAT, theme=HTML_TERMINAL_THEME)
301
- logger.warning(f"Wrote {file_size_str(output_path)} to '{output_path}'")
308
+ log_file_write(output_path)
302
309
 
303
310
 
304
311
  def _print_abbreviations_table() -> None:
@@ -1,26 +1,41 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epstein-files
3
- Version: 1.0.1
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/epstein_emails_house_oversight/).
22
- * Word counts for the emails and text messages are [here](https://michelcrypt4d4mus.github.io/epstein_text_messages/epstein_emails_word_count.html).
23
- * 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/epstein_files_nov_2025_cryptadamus_metadata.json)
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)
24
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.
25
40
 
26
41
 
@@ -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,,
@@ -0,0 +1,7 @@
1
+ [console_scripts]
2
+ epstein_diff=epstein_files:epstein_diff
3
+ epstein_dump_urls=epstein_files:epstein_dump_urls
4
+ epstein_generate=epstein_files:generate_html
5
+ epstein_search=epstein_files:epstein_search
6
+ epstein_show=epstein_files:epstein_show
7
+
@@ -1,30 +0,0 @@
1
- epstein_files/__init__.py,sha256=HKOGWVzyiEl0II8s7qnO33wcTZPw8dr3HDfT7_VnY2U,7831
2
- epstein_files/documents/communication.py,sha256=SunZdjMhR9v6y8LlQ6jhIu8vYjSndaBK0Su1mKnhfj0,2060
3
- epstein_files/documents/document.py,sha256=E2VUuHSoam8r8pyRbWy68_cVFU9DJ0irbtYULaDBYzE,16378
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=ruhl24FgOpEdcqbMcIpgyYlqEBi1ZeztQ7QmZMYANp0,17856
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=eSgHnOqMAy7Czfw1vJ3lvniDST7QpfuQ8XgzttXpAi8,10191
14
- epstein_files/util/constant/strings.py,sha256=3JTqD0luJrC3NbGXn4q6P-gIaaNVx36P1oCmp92gAoM,1750
15
- epstein_files/util/constant/urls.py,sha256=P_nnb5cIrJ2NCbd4CoRfilZ5jrOzFs30jNK50F7IjZw,5483
16
- epstein_files/util/constants.py,sha256=l0TrpncCJcecar5geggLVfOJVhicWcu3zws-62Cslzg,102472
17
- epstein_files/util/data.py,sha256=s7EgmQ6SC6MoUMhP8GaxglJOJpU_nRmwI-T2inLl8hs,3329
18
- epstein_files/util/doc_cfg.py,sha256=AFLBp9PO2tBUYhTeNztpkWs69VPr41fCuA22IesM59g,9914
19
- epstein_files/util/env.py,sha256=9wM4mKPZoJO9J5VKLP67ZR1IEMkH2UaGdIaYm2tptMQ,4581
20
- epstein_files/util/file_helper.py,sha256=ea428og2gyUsMWd2GFldmAGvuqGNNHaTc7gGGSM_0aI,3827
21
- epstein_files/util/highlighted_group.py,sha256=UO0pqQH29JHiOMK2psIozIeA0zinDkql-NYDGRmFUzA,35173
22
- epstein_files/util/logging.py,sha256=0mABrI5TvodLDe-9svHv3YaIdSyr8PXFcu4nntW1WsI,1858
23
- epstein_files/util/rich.py,sha256=cLkiSg3f68bUxobzRiBRfrgS7Ya-FK7ltpQcZXNHMX4,13317
24
- epstein_files/util/search_result.py,sha256=1fxe0KPBQXBk4dLfu6m0QXIzYfZCzvaSkWqvghJGzxY,567
25
- epstein_files/util/timer.py,sha256=8hxW4Y1JcTUfnBrHh7sL2pM9xu1sL4HFQM4CmmzTarU,837
26
- epstein_files/util/word_count.py,sha256=XTINgLm01jFQlNgdiLCcVFCodXAIb1dNbaAvznoRb1o,6757
27
- epstein_files-1.0.1.dist-info/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
28
- epstein_files-1.0.1.dist-info/METADATA,sha256=IK5B-ltx_L0-EK2Oq2Q2gVXWlhYqKoYw6HCFisqitvQ,3587
29
- epstein_files-1.0.1.dist-info/WHEEL,sha256=d2fvjOD7sXsVzChCqf0Ty0JbHKBaLYwDbGQDwQTnJ50,88
30
- epstein_files-1.0.1.dist-info/RECORD,,