epstein-files 1.0.12__tar.gz → 1.0.13__tar.gz

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.
Files changed (32) hide show
  1. {epstein_files-1.0.12 → epstein_files-1.0.13}/PKG-INFO +1 -1
  2. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/communication.py +2 -2
  3. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/document.py +23 -19
  4. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/email.py +8 -5
  5. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/messenger_log.py +2 -2
  6. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/epstein_files.py +27 -12
  7. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/names.py +4 -2
  8. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/urls.py +13 -8
  9. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constants.py +4 -2
  10. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/data.py +1 -1
  11. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/doc_cfg.py +2 -2
  12. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/highlighted_group.py +19 -17
  13. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/rich.py +11 -2
  14. {epstein_files-1.0.12 → epstein_files-1.0.13}/pyproject.toml +1 -1
  15. {epstein_files-1.0.12 → epstein_files-1.0.13}/LICENSE +0 -0
  16. {epstein_files-1.0.12 → epstein_files-1.0.13}/README.md +0 -0
  17. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/__init__.py +0 -0
  18. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/emails/email_header.py +0 -0
  19. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/imessage/text_message.py +0 -0
  20. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/json_file.py +0 -0
  21. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/documents/other_file.py +0 -0
  22. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/common_words.py +0 -0
  23. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/html.py +0 -0
  24. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/output_files.py +0 -0
  25. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/constant/strings.py +0 -0
  26. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/env.py +0 -0
  27. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/file_helper.py +0 -0
  28. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/logging.py +0 -0
  29. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/output.py +0 -0
  30. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/search_result.py +0 -0
  31. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/timer.py +0 -0
  32. {epstein_files-1.0.12 → epstein_files-1.0.13}/epstein_files/util/word_count.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: epstein-files
3
- Version: 1.0.12
3
+ Version: 1.0.13
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
@@ -34,9 +34,9 @@ class Communication(Document):
34
34
  def is_attribution_uncertain(self) -> bool:
35
35
  return bool(self.config and self.config.is_attribution_uncertain)
36
36
 
37
- def external_links(self, _style: str = '', include_alt_link: bool = True) -> Text:
37
+ def external_links(self, _style: str = '', include_alt_links: bool = True) -> Text:
38
38
  """Overrides super() method to apply self.author_style."""
39
- return super().external_links(self.author_style, include_alt_link=include_alt_link)
39
+ return super().external_links(self.author_style, include_alt_links=include_alt_links)
40
40
 
41
41
  def summary(self) -> Text:
42
42
  return self._summary().append(CLOSE_PROPERTIES_CHAR)
@@ -5,7 +5,7 @@ from dataclasses import asdict, dataclass, field
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
7
  from subprocess import run
8
- from typing import ClassVar, Sequence, TypeVar
8
+ from typing import Callable, ClassVar, Sequence, TypeVar
9
9
 
10
10
  from rich.console import Console, ConsoleOptions, Group, RenderResult
11
11
  from rich.padding import Padding
@@ -16,7 +16,7 @@ from epstein_files.util.constant.names import *
16
16
  from epstein_files.util.constant.strings import *
17
17
  from epstein_files.util.constant.urls import *
18
18
  from epstein_files.util.constants import ALL_FILE_CONFIGS, FALLBACK_TIMESTAMP
19
- from epstein_files.util.data import collapse_newlines, date_str, patternize, remove_time_from_timestamp_str, without_falsey
19
+ from epstein_files.util.data import collapse_newlines, date_str, patternize, remove_zero_time_from_timestamp_str, without_falsey
20
20
  from epstein_files.util.doc_cfg import DUPE_TYPE_STRS, EmailCfg, DocCfg, Metadata, TextCfg
21
21
  from epstein_files.util.env import DOCS_DIR, args
22
22
  from epstein_files.util.file_helper import (file_stem_for_id, extract_file_id, file_size,
@@ -122,39 +122,43 @@ class Document:
122
122
  return txt.append(epstein_media_doc_link_txt(self.config.dupe_of_id, style='royal_blue1'))
123
123
 
124
124
  def epsteinify_link(self, style: str = ARCHIVE_LINK_COLOR, link_txt: str | None = None) -> Text:
125
- """Create a Text obj link to this document on epsteinify.com."""
126
- return link_text_obj(epsteinify_doc_url(self.url_slug), link_txt or self.file_path.stem, style)
125
+ return self.external_url(epsteinify_doc_url, style, link_txt)
127
126
 
128
127
  def epstein_media_link(self, style: str = ARCHIVE_LINK_COLOR, link_txt: str | None = None) -> Text:
129
- """Create a Text obj link to this document on epstein.media."""
130
- return link_text_obj(epstein_media_doc_url(self.url_slug), link_txt or self.file_path.stem, style)
128
+ return self.external_url(epstein_media_doc_url, style, link_txt)
131
129
 
132
130
  def epstein_web_link(self, style: str = ARCHIVE_LINK_COLOR, link_txt: str | None = None) -> Text:
133
- """Create a Text obj link to this document on EpsteinWeb."""
134
- return link_text_obj(epstein_web_doc_url(self.url_slug), link_txt or self.file_path.stem, style)
131
+ return self.external_url(epstein_web_doc_url, style, link_txt)
135
132
 
136
- def external_links(self, style: str = '', include_alt_link: bool = False) -> Text:
133
+ def rollcall_link(self, style: str = ARCHIVE_LINK_COLOR, link_txt: str | None = None) -> Text:
134
+ return self.external_url(rollcall_doc_url, style, link_txt)
135
+
136
+ def external_url(self, fxn: Callable[[str], str], style: str = ARCHIVE_LINK_COLOR, link_txt: str | None = None) -> Text:
137
+ return link_text_obj(fxn(self.url_slug), link_txt or self.file_path.stem, style)
138
+
139
+ def external_links(self, style: str = '', include_alt_links: bool = False) -> Text:
137
140
  """Returns colored links to epstein.media and and epsteinweb in a Text object."""
138
- txt = Text('', style='white' if include_alt_link else ARCHIVE_LINK_COLOR)
141
+ txt = Text('', style='white' if include_alt_links else ARCHIVE_LINK_COLOR)
139
142
 
140
143
  if args.use_epstein_web:
141
144
  txt.append(self.epstein_web_link(style=style))
142
-
143
- if include_alt_link:
144
- txt.append(' (').append(self.epsteinify_link(style='white dim', link_txt=EPSTEINIFY)).append(')')
145
- txt.append(' (').append(self.epstein_media_link(style='white dim', link_txt=EPSTEIN_MEDIA)).append(')')
145
+ alt_link = self.epstein_media_link(style='white dim', link_txt=EPSTEIN_MEDIA)
146
146
  else:
147
147
  txt.append(self.epstein_media_link(style=style))
148
+ alt_link = self.epstein_web_link(style='white dim', link_txt=EPSTEIN_WEB)
149
+
150
+ if include_alt_links:
151
+ txt.append(' (').append(self.epsteinify_link(style='white dim', link_txt=EPSTEINIFY)).append(')')
152
+ txt.append(' (').append(alt_link).append(')')
148
153
 
149
- if include_alt_link:
150
- txt.append(' (').append(self.epsteinify_link(style='white dim', link_txt=EPSTEINIFY)).append(')')
151
- txt.append(' (').append(self.epstein_web_link(style='white dim', link_txt=EPSTEIN_WEB)).append(')')
154
+ if self._class_name() == 'Email':
155
+ txt.append(' (').append(self.rollcall_link(style='white dim', link_txt=ROLLCALL)).append(')')
152
156
 
153
157
  return txt
154
158
 
155
159
  def file_info_panel(self) -> Group:
156
160
  """Panel with filename linking to raw file plus any additional info about the file."""
157
- panel = Panel(self.external_links(include_alt_link=True), border_style=self._border_style(), expand=False)
161
+ panel = Panel(self.external_links(include_alt_links=True), border_style=self._border_style(), expand=False)
158
162
  padded_info = [Padding(sentence, INFO_PADDING) for sentence in self.info()]
159
163
  return Group(*([panel] + padded_info))
160
164
 
@@ -243,7 +247,7 @@ class Document:
243
247
  txt.append(f" {self.url_slug}", style=FILENAME_STYLE)
244
248
 
245
249
  if self.timestamp:
246
- timestamp_str = remove_time_from_timestamp_str(self.timestamp)
250
+ timestamp_str = remove_zero_time_from_timestamp_str(self.timestamp).replace('T', ' ')
247
251
  txt.append(' (', style=SYMBOL_STYLE)
248
252
  txt.append(f"{timestamp_str}", style=TIMESTAMP_DIM).append(')', style=SYMBOL_STYLE)
249
253
 
@@ -132,7 +132,6 @@ JUNK_EMAILERS = [
132
132
  'How To Academy',
133
133
  'Jokeland',
134
134
  JP_MORGAN_USGIO,
135
- 'Saved by Internet Explorer 11',
136
135
  ]
137
136
 
138
137
  MAILING_LISTS = [
@@ -274,11 +273,9 @@ USELESS_EMAILERS = FLIGHT_IN_2012_PEOPLE + IRAN_DEAL_RECIPIENTS + KRASSNER_RECIP
274
273
  'Michael Simmons', # Random CC
275
274
  'Nancy Portland', # Lawrence Krauss CC
276
275
  'Oliver Goodenough', # Robert Trivers CC
277
- 'Owen Blicksilver', # Landon Thomas CC
278
276
  'Peter Aldhous', # Lawrence Krauss CC
279
277
  'Sam Harris', # Lawrence Krauss CC
280
278
  SAMUEL_LEFF, # Random CC
281
- "Saved by Internet Explorer 11",
282
279
  'Sean T Lehane', # Random CC
283
280
  'Stephen Rubin', # Random CC
284
281
  'Tim Kane', # Random CC
@@ -358,8 +355,12 @@ class Email(Communication):
358
355
  self.actual_text = self._actual_text()
359
356
  self.sent_from_device = self._sent_from_device()
360
357
 
358
+ def attachments(self) -> list[str]:
359
+ return (self.header.attachments or '').split(';')
360
+
361
361
  def info_txt(self) -> Text:
362
- txt = Text("OCR text of email from ", style='grey46').append(self.author_txt).append(' to ')
362
+ email_type = 'fwded article' if self.is_fwded_article() else 'email'
363
+ txt = Text(f"OCR text of {email_type} from ", style='grey46').append(self.author_txt).append(' to ')
363
364
  return txt.append(self._recipients_txt()).append(highlighter(f" probably sent at {self.timestamp}"))
364
365
 
365
366
  def is_fwded_article(self) -> bool:
@@ -584,7 +585,7 @@ class Email(Communication):
584
585
  self._merge_lines(2, 5)
585
586
  elif self.file_id in ['029498', '031428']:
586
587
  self._merge_lines(2, 4)
587
- elif self.file_id in ['029976', '023067']:
588
+ elif self.file_id in ['029976', '023067', '033576']:
588
589
  self._merge_lines(3) # Merge 4th and 5th rows
589
590
  elif self.file_id in '026609 029402 032405 022695'.split():
590
591
  self._merge_lines(4) # Merge 5th and 6th rows
@@ -609,6 +610,8 @@ class Email(Communication):
609
610
  self._merge_lines(7, 9)
610
611
  elif self.file_id == '030299':
611
612
  self._merge_lines(7, 10)
613
+ elif self.file_id in ['022673', '022684']:
614
+ self._merge_lines(9)
612
615
  elif self.file_id == '014860':
613
616
  self._merge_lines(3)
614
617
  self._merge_lines(4)
@@ -16,7 +16,7 @@ from epstein_files.util.data import iso_timestamp, listify, sort_dict
16
16
  from epstein_files.util.doc_cfg import Metadata, TextCfg
17
17
  from epstein_files.util.highlighted_group import get_style_for_name
18
18
  from epstein_files.util.logging import logger
19
- from epstein_files.util.rich import build_table, highlighter
19
+ from epstein_files.util.rich import LAST_TIMESTAMP_STYLE, build_table, highlighter
20
20
 
21
21
  CONFIRMED_MSG = 'Found confirmed counterparty'
22
22
  GUESSED_MSG = 'This is probably a conversation with'
@@ -130,7 +130,7 @@ class MessengerLog(Communication):
130
130
  counts_table.add_column('Files', justify='right', style='white')
131
131
  counts_table.add_column("Msgs", justify='right')
132
132
  counts_table.add_column('First Sent At', justify='center', highlight=True, width=21)
133
- counts_table.add_column('Last Sent At', justify='center', style='wheat4', width=21)
133
+ counts_table.add_column('Last Sent At', justify='center', style=LAST_TIMESTAMP_STYLE, width=21)
134
134
  counts_table.add_column('Days', justify='right', style='dim')
135
135
 
136
136
  for name, count in sort_dict(cls.count_authors(imessage_logs)):
@@ -23,12 +23,12 @@ from epstein_files.util.constant.strings import *
23
23
  from epstein_files.util.constant.urls import (EPSTEIN_MEDIA, EPSTEIN_WEB, JMAIL, epstein_media_person_url,
24
24
  epsteinify_name_url, epstein_web_person_url, search_jmail_url, search_twitter_url)
25
25
  from epstein_files.util.constants import *
26
- from epstein_files.util.data import dict_sets_to_lists, json_safe, listify, sort_dict
26
+ from epstein_files.util.data import dict_sets_to_lists, iso_timestamp, json_safe, listify, sort_dict
27
27
  from epstein_files.util.doc_cfg import EmailCfg, Metadata
28
28
  from epstein_files.util.env import DOCS_DIR, args, logger
29
29
  from epstein_files.util.file_helper import file_size_str
30
30
  from epstein_files.util.highlighted_group import get_info_for_name, get_style_for_name
31
- from epstein_files.util.rich import (DEFAULT_NAME_STYLE, NA_TXT, add_cols_to_table,
31
+ from epstein_files.util.rich import (DEFAULT_NAME_STYLE, LAST_TIMESTAMP_STYLE, NA_TXT, add_cols_to_table,
32
32
  build_table, console, highlighter, link_text_obj, link_markup, print_author_header, print_centered,
33
33
  print_other_site_link, print_panel, print_section_header, vertically_pad)
34
34
  from epstein_files.util.search_result import SearchResult
@@ -278,25 +278,40 @@ class EpsteinFiles:
278
278
  def print_emailer_counts_table(self) -> None:
279
279
  footer = f"Identified authors of {self.attributed_email_count():,} out of {len(self.emails):,} emails ."
280
280
  counts_table = build_table("Email Counts", caption=footer)
281
- add_cols_to_table(counts_table, ['Name', 'Count', 'Sent', "Recv'd", JMAIL, EPSTEIN_MEDIA, EPSTEIN_WEB, 'Twitter'])
281
+
282
+ add_cols_to_table(counts_table, [
283
+ 'Name',
284
+ 'Num',
285
+ 'Sent',
286
+ "Recv",
287
+ {'name': 'First', 'highlight': True},
288
+ {'name': 'Last', 'style': LAST_TIMESTAMP_STYLE},
289
+ JMAIL,
290
+ 'eMedia',
291
+ 'eWeb',
292
+ 'Twitter',
293
+ ])
282
294
 
283
295
  emailer_counts = {
284
296
  emailer: self.email_author_counts[emailer] + self.email_recipient_counts[emailer]
285
297
  for emailer in self.all_emailers(True)
286
298
  }
287
299
 
288
- for p, count in sort_dict(emailer_counts):
289
- style = get_style_for_name(p, default_style=DEFAULT_NAME_STYLE)
300
+ for name, count in sort_dict(emailer_counts):
301
+ style = get_style_for_name(name, default_style=DEFAULT_NAME_STYLE)
302
+ emails = self.emails_for(name)
290
303
 
291
304
  counts_table.add_row(
292
- Text.from_markup(link_markup(epsteinify_name_url(p or UNKNOWN), p or UNKNOWN, style)),
305
+ Text.from_markup(link_markup(epsteinify_name_url(name or UNKNOWN), name or UNKNOWN, style)),
293
306
  str(count),
294
- str(self.email_author_counts[p]),
295
- str(self.email_recipient_counts[p]),
296
- '' if p is None else link_text_obj(search_jmail_url(p), JMAIL),
297
- '' if not is_ok_for_epstein_web(p) else link_text_obj(epstein_media_person_url(p), EPSTEIN_MEDIA),
298
- '' if not is_ok_for_epstein_web(p) else link_text_obj(epstein_web_person_url(p), EPSTEIN_WEB),
299
- '' if p is None else link_text_obj(search_twitter_url(p), 'search X'),
307
+ str(self.email_author_counts[name]),
308
+ str(self.email_recipient_counts[name]),
309
+ emails[0].timestamp_without_seconds(),
310
+ emails[-1].timestamp_without_seconds(),
311
+ '' if name is None else link_text_obj(search_jmail_url(name), JMAIL),
312
+ '' if not is_ok_for_epstein_web(name) else link_text_obj(epstein_media_person_url(name), 'eMedia'),
313
+ '' if not is_ok_for_epstein_web(name) else link_text_obj(epstein_web_person_url(name), 'eWeb'),
314
+ '' if name is None else link_text_obj(search_twitter_url(name), 'search X'),
300
315
  )
301
316
 
302
317
  console.print(vertically_pad(counts_table, 2))
@@ -42,6 +42,7 @@ CECILE_DE_JONGH = 'Cecile de Jongh'
42
42
  CECILIA_STEEN = 'Cecilia Steen'
43
43
  CELINA_DUBIN = 'Celina Dubin'
44
44
  CHRISTINA_GALBRAITH = 'Christina Galbraith' # Works with Tyler Shears on reputation stuff
45
+ DANGENE_AND_JENNIE_ENTERPRISE = 'Dangene and Jennie Enterprise'
45
46
  DANIEL_SABBA = 'Daniel Sabba'
46
47
  DANIEL_SIAD = 'Daniel Siad'
47
48
  DANNY_FROST = 'Danny Frost'
@@ -233,13 +234,14 @@ OTHER_NAMES = NAMES_TO_NOT_HIGHLIGHT + """
233
234
  edmond elizabeth emily entwistle erik evelyn
234
235
  ferguson flachsbart francis franco frank frost
235
236
  gardner gary geoff geoffrey gilbert gloria goldberg gonzalez gould graham greene guarino gwyneth
236
- hancock harold harrison harry hay helen hirsch hofstadter horowitz hussein
237
+ hancock harold harrison harry hay helen hill hirsch hofstadter horowitz hussein
237
238
  ian isaac isaacson
238
- jamie jane janet jason jen jim joe johnson jones josh julie justin
239
+ james jamie jane janet jason jen jim joe johnson jones josh julie justin
239
240
  karl kate kathy kelly kim kruger kyle
240
241
  laurie leo leonard lenny leslie lieberman louis lynch lynn
241
242
  marcus marianne matt matthew melissa michele michelle moore moscowitz
242
243
  nancy nicole nussbaum
244
+ owen
243
245
  paulson philippe
244
246
  rafael ray richard richardson rob robin ron rubin rudolph ryan
245
247
  sara sarah sean seligman serge sergey silverman sloman smith snowden sorkin steele stevie stewart
@@ -13,11 +13,12 @@ ARCHIVE_LINK_COLOR = 'slate_blue3'
13
13
  TEXT_LINK = 'text_link'
14
14
 
15
15
  # External site names
16
- ExternalSite = Literal['epstein.media', 'epsteinify', 'EpsteinWeb']
16
+ ExternalSite = Literal['epstein.media', 'epsteinify', 'EpsteinWeb', 'RollCall']
17
17
  EPSTEIN_MEDIA = 'epstein.media'
18
18
  EPSTEIN_WEB = 'EpsteinWeb'
19
19
  EPSTEINIFY = 'epsteinify'
20
20
  JMAIL = 'Jmail'
21
+ ROLLCALL = 'RollCall'
21
22
 
22
23
  GH_PROJECT_URL = 'https://github.com/michelcrypt4d4mus/epstein_text_messages'
23
24
  GH_MASTER_URL = f"{GH_PROJECT_URL}/blob/master"
@@ -41,9 +42,10 @@ EPSTEIN_WEB_URL = 'https://epsteinweb.org'
41
42
  JMAIL_URL = 'https://jmail.world'
42
43
 
43
44
  DOC_LINK_BASE_URLS: dict[ExternalSite, str] = {
44
- EPSTEIN_MEDIA: f"{EPSTEIN_MEDIA_URL}/files",
45
- EPSTEIN_WEB: f'{EPSTEIN_WEB_URL}/wp-content/uploads/epstein_evidence/images',
46
- EPSTEINIFY: f"{EPSTEINIFY_URL}/document",
45
+ EPSTEIN_MEDIA: f"{EPSTEIN_MEDIA_URL}/files/",
46
+ EPSTEIN_WEB: f'{EPSTEIN_WEB_URL}/wp-content/uploads/epstein_evidence/images/',
47
+ EPSTEINIFY: f"{EPSTEINIFY_URL}/document/",
48
+ ROLLCALL: f'https://rollcall.com/factbase/epstein/file?id=',
47
49
  }
48
50
 
49
51
 
@@ -53,7 +55,7 @@ epsteinify_doc_link_txt = lambda filename_or_id, style = TEXT_LINK: Text.from_ma
53
55
  epsteinify_doc_url = lambda file_stem: build_doc_url(DOC_LINK_BASE_URLS[EPSTEINIFY], file_stem)
54
56
  epsteinify_name_url = lambda name: f"{EPSTEINIFY_URL}/?name={urllib.parse.quote(name)}"
55
57
 
56
- epstein_media_doc_url = lambda file_stem: build_doc_url(DOC_LINK_BASE_URLS[EPSTEIN_MEDIA], file_stem, True)
58
+ epstein_media_doc_url = lambda file_stem: build_doc_url(DOC_LINK_BASE_URLS[EPSTEIN_MEDIA], file_stem, 'lower')
57
59
  epstein_media_doc_link_markup = lambda filename_or_id, style = TEXT_LINK: external_doc_link_markup(EPSTEIN_MEDIA, filename_or_id, style)
58
60
  epstein_media_doc_link_txt = lambda filename_or_id, style = TEXT_LINK: Text.from_markup(epstein_media_doc_link_markup(filename_or_id, style))
59
61
  epstein_media_person_url = lambda person: f"{EPSTEIN_MEDIA_URL}/people/{parameterize(person)}"
@@ -62,16 +64,19 @@ epstein_web_doc_url = lambda file_stem: f"{DOC_LINK_BASE_URLS[EPSTEIN_WEB]}/{fil
62
64
  epstein_web_person_url = lambda person: f"{EPSTEIN_WEB_URL}/{parameterize(person)}"
63
65
  epstein_web_search_url = lambda s: f"{EPSTEIN_WEB_URL}/?ewmfileq={urllib.parse.quote(s)}&ewmfilepp=20"
64
66
 
67
+ rollcall_doc_url = lambda file_stem: build_doc_url(DOC_LINK_BASE_URLS[ROLLCALL], file_stem, 'title')
68
+
65
69
  search_archive_url = lambda txt: f"{COURIER_NEWSROOM_ARCHIVE_URL}&q={urllib.parse.quote(txt)}&p=1"
66
70
  search_coffeezilla_url = lambda txt: f"{COFFEEZILLA_ARCHIVE_URL}&q={urllib.parse.quote(txt)}&p=1"
67
71
  search_jmail_url = lambda txt: f"{JMAIL_URL}/search?q={urllib.parse.quote(txt)}"
68
72
  search_twitter_url = lambda txt: f"https://x.com/search?q={urllib.parse.quote(txt)}&src=typed_query&f=live"
69
73
 
70
74
 
71
- def build_doc_url(base_url: str, filename_or_id: int | str, lowercase: bool = False) -> str:
75
+ def build_doc_url(base_url: str, filename_or_id: int | str, case: Literal['lower', 'title'] | None = None) -> str:
72
76
  file_stem = coerce_file_stem(filename_or_id)
73
- file_stem = file_stem.lower() if lowercase else file_stem
74
- return f"{base_url}/{file_stem}"
77
+ file_stem = file_stem.lower() if case == 'lower' else file_stem
78
+ file_stem = file_stem.title() if case == 'title' else file_stem
79
+ return f"{base_url}{file_stem}"
75
80
 
76
81
 
77
82
  def external_doc_link_markup(site: ExternalSite, filename_or_id: int | str, style: str = TEXT_LINK) -> str:
@@ -65,7 +65,7 @@ EMAILER_ID_REGEXES: dict[str, re.Pattern] = {
65
65
  BORIS_NIKOLIC: re.compile(r'(boris )?nikolic?', re.IGNORECASE),
66
66
  BRAD_EDWARDS: re.compile(r'Brad(ley)?(\s*J(.?|ames))?\s*Edwards', re.IGNORECASE),
67
67
  BRAD_KARP: re.compile(r'Brad (S.? )?Karp|Karp, Brad', re.IGNORECASE),
68
- 'Dangene and Jennie Enterprise': re.compile(r'Dangene and Jennie Enterprise?', re.IGNORECASE),
68
+ DANGENE_AND_JENNIE_ENTERPRISE: re.compile(r'Dangene and Jennie Enterprise?', re.IGNORECASE),
69
69
  DANNY_FROST: re.compile(r'Frost, Danny|frostd@dany.nyc.gov|Danny\s*Frost', re.IGNORECASE),
70
70
  DARREN_INDYKE: re.compile(r'darren$|Darren\s*(K\.?\s*)?[il]n[dq]_?yke?|dkiesq', re.IGNORECASE),
71
71
  DAVID_FISZEL: re.compile(r'David\s*Fis?zel', re.IGNORECASE),
@@ -714,6 +714,7 @@ EMAILS_CONFIG = [
714
714
  EmailCfg(id='026580', is_fwded_article=True), # NPR: Antigua: Land Of Sun, Sand, And Super Cheap
715
715
  EmailCfg(id='031340', is_fwded_article=True), # Article about Alex Jones threatening Robert Mueller
716
716
  EmailCfg(id='030209', is_fwded_article=True), # Atlantic Council Syria: Blackberry Diplomacy
717
+ EmailCfg(id='026605', is_fwded_article=True), # Article about Ruemmler turning down attorney general job by NEDRA PICKLER
717
718
  EmailCfg(id='033297', is_fwded_article=True, duplicate_ids=['033586']), # Sultan Sulayem fwding article about Trump and Russia
718
719
  EmailCfg(id='032475', timestamp=parse('2017-02-15 13:31:25')),
719
720
  EmailCfg(id='030373', timestamp=parse('2018-10-03 01:49:27')),
@@ -929,6 +930,7 @@ OTHER_FILES_ARTICLES = [
929
930
  DocCfg(id='029865', author=LA_TIMES, description=f"front page article about {DEEPAK_CHOPRA} and young Iranians", date='2016-11-05'),
930
931
  DocCfg(id='026598', author=LA_TIMES, description=f"op-ed about why America needs a Ministry of Culture"),
931
932
  DocCfg(id='027024', author=LA_TIMES, description=f"Scientists Create Human Embryos to Make Stem Cells", date='2013-05-15'),
933
+ DocCfg(id='022811', author='Law.com', description='Sarah Ransome Identifies Herself in Epstein Sex Trafficking Case', date='2018-01-09'),
932
934
  DocCfg(id='031776', author='Law360', description=f"article about Michael Avenatti by Andrew Strickler"),
933
935
  DocCfg(id='023102', author=f'Litigation Daily', description=f"article about {REID_WEINGARTEN}", date='2015-09-04'),
934
936
  DocCfg(id='029340', author=f'MarketWatch', description=f'article about estate taxes, particularly Epstein\'s favoured GRATs'),
@@ -1190,7 +1192,7 @@ OTHER_FILES_LEGAL = [
1190
1192
  ]
1191
1193
 
1192
1194
  OTHER_FILES_CONFERENCES = [
1193
- DocCfg(id='014315', author=BOFA_MERRILL, description=f'2016 Future of Financials Conference'),
1195
+ DocCfg(id='014315', author=BOFA_MERRILL, description=f'2016 Future of Financials Conference, attached to 014312'),
1194
1196
  DocCfg(id='026825', author=DEUTSCHE_BANK, description=f"Asset & Wealth Management featured speaker bios"), # Really "Deutsche Asset" which may not be Deutsche Bank?
1195
1197
  DocCfg(id='023123', author=LAWRENCE_KRAUSS_ASU_ORIGINS, description=f"{STRANGE_BEDFELLOWS} (old draft)"),
1196
1198
  DocCfg(id='023120', author=LAWRENCE_KRAUSS_ASU_ORIGINS, description=STRANGE_BEDFELLOWS, duplicate_ids=['023121'], dupe_type='earlier'),
@@ -26,7 +26,7 @@ date_str = lambda dt: dt.isoformat()[0:10] if dt else None
26
26
  escape_double_quotes = lambda text: text.replace('"', r'\"')
27
27
  escape_single_quotes = lambda text: text.replace("'", r"\'")
28
28
  iso_timestamp = lambda dt: dt.isoformat().replace('T', ' ')
29
- remove_time_from_timestamp_str = lambda dt: dt.isoformat().removesuffix('T00:00:00')
29
+ remove_zero_time_from_timestamp_str = lambda dt: dt.isoformat().removesuffix('T00:00:00')
30
30
  uniquify = lambda _list: list(set(_list))
31
31
  without_falsey = lambda _list: [e for e in _list if e]
32
32
 
@@ -8,7 +8,7 @@ from dateutil.parser import parse
8
8
 
9
9
  from epstein_files.util.constant.names import *
10
10
  from epstein_files.util.constant.strings import *
11
- from epstein_files.util.data import remove_time_from_timestamp_str, without_falsey
11
+ from epstein_files.util.data import remove_zero_time_from_timestamp_str, without_falsey
12
12
 
13
13
  DuplicateType = Literal['earlier', 'quoted', 'redacted', 'same']
14
14
  Metadata = dict[str, bool | datetime | int | str | list[str | None] |dict[str, bool | str]]
@@ -147,7 +147,7 @@ class DocCfg:
147
147
  elif _field.name == 'timestamp' and self.date is not None:
148
148
  continue # Don't print both timestamp and date
149
149
  elif isinstance(value, datetime):
150
- value_str = remove_time_from_timestamp_str(value)
150
+ value_str = remove_zero_time_from_timestamp_str(value)
151
151
  add_prop(_field, f"parse('{value_str}')" if CONSTANTIZE_NAMES else f"'{value}'")
152
152
  elif isinstance(value, str):
153
153
  if "'" in value:
@@ -299,23 +299,10 @@ HIGHLIGHTED_NAMES = [
299
299
  KEN_STARR: 'head of the Monica Lewinsky investigation against Bill Clinton',
300
300
  }
301
301
  ),
302
- HighlightedNames(
303
- label='friend',
304
- style='tan',
305
- pattern=r"Andrew Farkas|Thomas\s*(J\.?\s*)?Barrack(\s*Jr)?",
306
- emailers = {
307
- DAVID_STERN: f'emailed Epstein from Moscow, appears to know chairman of {DEUTSCHE_BANK}',
308
- JONATHAN_FARKAS: "heir to the Alexander's department store fortune",
309
- 'linkspirit': "Skype username of someone Epstein communicated with",
310
- 'Peter Thomas Roth': 'student of Epstein at Dalton, skincare company founder',
311
- STEPHEN_HANSON: None,
312
- TOM_BARRACK: 'long time friend of Trump',
313
- }
314
- ),
315
302
  HighlightedNames(
316
303
  label=FINANCE,
317
304
  style='green',
318
- pattern=r'Apollo|Ari\s*Glass|Bank|(Bernie\s*)?Madoff|Black(rock|stone)|B\s*of\s*A|Boothbay(\sFund\sManagement)?|Chase\s*Bank|Credit\s*Suisse|DB|Deutsche\s*(Asset|Bank)|Electron\s*Capital\s*(Partners)?|Fenner|FRBNY|Goldman(\s*Sachs)|HSBC|Invesco|(Janet\s*)?Yellen|(Jerome\s*)?Powell(?!M\. Cabot)|(Jimmy\s*)?Cayne|JPMC?|j\.?p\.?\s*morgan(\.?com|\s*Chase)?|Madoff|Merrill(\s*Lynch)?|(Michael\s*)?(Cembalest|Milken)|Mizrahi\s*Bank|MLPF&S|((anti.?)?money\s+)?launder(s?|ers?|ing)?(\s+money)?|Morgan Stanley|(Peter L. )?Scher|(Ray\s*)?Dalio|Schwartz?man|Serageldin|UBS|us.gio@jpmorgan.com',
305
+ pattern=r'Apollo|Ari\s*Glass|Bank|(Bernie\s*)?Madoff|Black(rock|stone)|B\s*of\s*A|Boothbay(\sFund\sManagement)?|Chase\s*Bank|Credit\s*Suisse|DB|Deutsche\s*(Asset|Bank)|Electron\s*Capital\s*(Partners)?|Fenner|FRBNY|Goldman(\s*Sachs)|HSBC|Invesco|(Janet\s*)?Yellen|(Jerome\s*)?Powell(?!M\. Cabot)|(Jimmy\s*)?Cayne|JPMC?|j\.?p\.?\s*morgan(\.?com|\s*Chase)?|Madoff|Merrill(\s*Lynch)?|(Michael\s*)?(Cembalest|Milken)|Mizrahi\s*Bank|MLPF&S|((anti.?)?money\s+)?launder(s?|ers?|ing)?(\s+money)?|Morgan Stanley|(Peter L. )?Scher|(Ray\s*)?Dalio|(Richard\s*)?LeFrak|Schwartz?man|Serageldin|UBS|us.gio@jpmorgan.com',
319
306
  emailers={
320
307
  AMANDA_ENS: 'Citigroup',
321
308
  BRAD_WECHSLER: f"head of {LEON_BLACK}'s personal investment vehicle according to FT",
@@ -332,9 +319,23 @@ HIGHLIGHTED_NAMES = [
332
319
  PAUL_MORRIS: 'Deutsche Bank',
333
320
  }
334
321
  ),
322
+ HighlightedNames(
323
+ label='friend',
324
+ style='tan',
325
+ pattern=r"Andrew Farkas|Thomas\s*(J\.?\s*)?Barrack(\s*Jr)?",
326
+ emailers = {
327
+ DANGENE_AND_JENNIE_ENTERPRISE: 'founders of the members-only CORE club',
328
+ DAVID_STERN: f'emailed Epstein from Moscow, appears to know chairman of {DEUTSCHE_BANK}',
329
+ JONATHAN_FARKAS: "heir to the Alexander's department store fortune",
330
+ 'linkspirit': "Skype username of someone Epstein communicated with",
331
+ 'Peter Thomas Roth': 'student of Epstein at Dalton, skincare company founder',
332
+ STEPHEN_HANSON: None,
333
+ TOM_BARRACK: 'long time friend of Trump',
334
+ },
335
+ ),
335
336
  HighlightedNames(
336
337
  label=HARVARD.lower(),
337
- style='deep_pink2',
338
+ style='light_goldenrod3',
338
339
  pattern=r'Cambridge|(Derek\s*)?Bok|Elisa(\s*New)?|Harvard(\s*(Business|Law|University)(\s*School)?)?|(Jonathan\s*)?Zittrain|(Stephen\s*)?Kosslyn',
339
340
  emailers = {
340
341
  "Donald Rubin": f"Professor of Statistics",
@@ -403,7 +404,7 @@ HIGHLIGHTED_NAMES = [
403
404
  HighlightedNames(
404
405
  label='law enforcement',
405
406
  style='color(24) bold',
406
- pattern=r'ag|(Alicia\s*)?Valle|AML|attorney|((Bob|Robert)\s*)?Mueller|(Byung\s)?Pak|CFTC?|CIA|CIS|CVRA|Dep(artmen)?t\.?\s*of\s*(the\s*)?(Justice|Treasury)|DHS|DOJ|FBI|FCPA|FDIC|Federal\s*Bureau\s*of\s*Investigation|FinCEN|FINRA|FOIA|FTC|IRS|(James\s*)?Comey|(Jennifer\s*Shasky\s*)?Calvery|((Judge|Mark)\s*)?(Carney|Filip)|(Kirk )?Blouin|KYC|NIH|NS(A|C)|OCC|OFAC|(Lann?a\s*)?Belohlavek|lawyer|(Michael\s*)?Reiter|OGE|Office\s*of\s*Government\s*Ethics|Police Code Enforcement|(Preet\s*)?Bharara|SCOTUS|SD(FL|NY)|Southern\s*District\s*of\s*(Florida|New\s*York)|SEC|Secret\s*Service|Securities\s*and\s*Exchange\s*Commission|State\s*Dep(artmen)?t|Strzok|Supreme\s*Court|Treasury\s*(Dep(artmen)?t|Secretary)|TSA|USAID|(William\s*J\.?\s*)?Zloch',
407
+ pattern=r'ag|(Alicia\s*)?Valle|AML|(Andrew\s*)?McCabe|attorney|((Bob|Robert)\s*)?Mueller|(Byung\s)?Pak|CFTC?|CIA|CIS|CVRA|Dep(artmen)?t\.?\s*of\s*(the\s*)?(Justice|Treasury)|DHS|DOJ|FBI|FCPA|FDIC|Federal\s*Bureau\s*of\s*Investigation|FinCEN|FINRA|FOIA|FTC|IRS|(James\s*)?Comey|(Jennifer\s*Shasky\s*)?Calvery|((Judge|Mark)\s*)?(Carney|Filip)|(Kirk )?Blouin|KYC|NIH|NS(A|C)|OCC|OFAC|(Lann?a\s*)?Belohlavek|lawyer|(Michael\s*)?Reiter|OGE|Office\s*of\s*Government\s*Ethics|Police Code Enforcement|(Preet\s*)?Bharara|SCOTUS|SD(FL|NY)|Southern\s*District\s*of\s*(Florida|New\s*York)|SEC|Secret\s*Service|Securities\s*and\s*Exchange\s*Commission|State\s*Dep(artmen)?t|Strzok|Supreme\s*Court|Treasury\s*(Dep(artmen)?t|Secretary)|TSA|USAID|(William\s*J\.?\s*)?Zloch',
407
408
  emailers = {
408
409
  ANN_MARIE_VILLAFANA: 'southern district of Florida U.S. Attorney',
409
410
  DANNY_FROST: 'Director of Communications at Manhattan DA',
@@ -462,6 +463,7 @@ HIGHLIGHTED_NAMES = [
462
463
  CHRISTINA_GALBRAITH: f"{REPUTATION_MGMT}, worked on Epstein's Google search results with {TYLER_SHEARS}",
463
464
  IAN_OSBORNE: f"{OSBORNE_LLP} reputation repairer possibly hired by Epstein ca. 2011-06",
464
465
  MICHAEL_SITRICK: 'crisis PR',
466
+ 'Owen Blicksilver': 'OBPR, Inc.',
465
467
  PEGGY_SIEGAL: 'socialite',
466
468
  'R. Couri Hay': None,
467
469
  ROSS_GOW: 'Acuity Reputation Management',
@@ -471,7 +473,7 @@ HIGHLIGHTED_NAMES = [
471
473
  HighlightedNames(
472
474
  label='republicans',
473
475
  style='bold dark_red',
474
- pattern=r'Alberto\sGonzale[sz]|(Alex\s*)?Acosta|(Bill\s*)?Barr|Bill\s*Shine|(Bob\s*)?Corker|(John\s*(R.?\s*)?)Bolton|Broidy|(Chris\s)?Christie|Devin\s*Nunes|(Don\s*)?McGa[hn]n|McMaster|(George\s*)?Nader|GOP|(Brett\s*)?Kavanaugh|Kissinger|Kobach|Koch\s*Brothers|Kolfage|Kudlow|Lewandowski|(Marco\s)?Rubio|(Mark\s*)Meadows|Mattis|McCain|(?<!Merwin Dela )Cruz|(Michael\s)?Hayden|((General|Mike)\s*)?(Flynn|Pence)|(Mitt\s*)?Romney|Mnuchin|Nikki|Haley|(Paul\s+)?(Manafort|Volcker)|(Peter\s)?Navarro|Pompeo|Reagan|Reince|Priebus|Republican|(Rex\s*)?Tillerson|(?<!Cynthia )(Richard\s*)?Nixon|Sasse',
476
+ pattern=r'Alberto\sGonzale[sz]|(Alex\s*)?Acosta|(Bill\s*)?Barr|Bill\s*Shine|(Bob\s*)?Corker|(John\s*(R.?\s*)?)Bolton|Broidy|(Chris\s)?Christie|Devin\s*Nunes|(Don\s*)?McGa[hn]n|McMaster|(George\s*)?Nader|GOP|(Brett\s*)?Kavanaugh|Kissinger|Kobach|Koch\s*Brothers|Kolfage|Kudlow|Lewandowski|(Marco\s)?Rubio|(Mark\s*)Meadows|Mattis|McCain|(?<!Merwin Dela )Cruz|(Michael\s)?Hayden|((General|Mike)\s*)?(Flynn|Pence)|(Mitt\s*)?Romney|Mnuchin|Nikki|Haley|(Paul\s+)?(Manafort|Volcker)|(Peter\s)?Navarro|Pompeo|Reagan|Reince|Priebus|Republican|(Rex\s*)?Tillerson|(?<!Cynthia )(Richard\s*)?Nixon|Sasse|Tea\s*Party',
475
477
  # There's no emails from these people, they're just here to automate the regex creation for both first + last names
476
478
  emailers = {
477
479
  RUDY_GIULIANI: 'disbarred formed mayor of New York City',
@@ -33,6 +33,7 @@ GREY_NUMBERS = [58, 39, 39, 35, 30, 27, 23, 23, 19, 19, 15, 15, 15]
33
33
  DEFAULT_NAME_STYLE = 'gray46'
34
34
  INFO_STYLE = 'white dim italic'
35
35
  KEY_STYLE='honeydew2 bold'
36
+ LAST_TIMESTAMP_STYLE='wheat4'
36
37
  SECTION_HEADER_STYLE = 'bold white on blue3'
37
38
  SOCIAL_MEDIA_LINK_STYLE = 'pale_turquoise4'
38
39
  SUBSTACK_POST_LINK_STYLE = 'bright_cyan'
@@ -79,10 +80,18 @@ console = Console(**CONSOLE_ARGS)
79
80
  highlighter = CONSOLE_ARGS['highlighter']
80
81
 
81
82
 
82
- def add_cols_to_table(table: Table, col_names: list[str]) -> None:
83
+ def add_cols_to_table(table: Table, col_names: list[str | dict]) -> None:
83
84
  """Left most col will be left justified, rest are center justified."""
84
85
  for i, col in enumerate(col_names):
85
- table.add_column(col, justify='left' if i == 0 else 'center')
86
+ if isinstance(col, dict):
87
+ col_name = col['name']
88
+ kwargs = col
89
+ del kwargs['name']
90
+ else:
91
+ col_name = col
92
+ kwargs = {}
93
+
94
+ table.add_column(col_name, justify='left' if i == 0 else 'center', **kwargs)
86
95
 
87
96
 
88
97
  def build_highlighter(pattern: str) -> EpsteinHighlighter:
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "epstein-files"
3
- version = "1.0.12"
3
+ version = "1.0.13"
4
4
  description = "Tools for working with the Jeffrey Epstein documents released in November 2025."
5
5
  authors = ["Michel de Cryptadamus"]
6
6
  readme = "README.md"
File without changes
File without changes