"""WikiLeaks-specific crawl helpers for Mosaic collectors.""" import re import logging from urllib.parse import urljoin from typing import Optional logger = logging.getLogger(__name__) def extract_extra_links(html: str, base_url: str, source_name: str) -> list[str]: """Extract additional links using WikiLeaks-specific patterns. Different WikiLeaks collections have different page structures. This function handles the quirks of each. """ links = [] if source_name == 'vault7': links.extend(_vault7_links(html, base_url)) elif source_name == 'vault8': links.extend(_vault8_links(html, base_url)) elif source_name == 'cablegate': links.extend(_cablegate_links(html, base_url)) elif source_name == 'spyfiles': links.extend(_spyfiles_links(html, base_url)) elif source_name == 'emails': links.extend(_email_links(html, base_url)) return links def _vault7_links(html: str, base_url: str) -> list[str]: """Extract Vault 7 project page links. Vault 7 has a main index with project names that link to sub-pages. Each project page has document links. """ links = [] # Match project links like /vault7/#Dark Matter for match in re.finditer(r'href=["\'](/vault7/[^"\']+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) # Match document/file links for match in re.finditer(r'href=["\']([^"\']*(?:\.pdf|\.zip|\.tar\.gz)[^"\']*)["\']', html, re.IGNORECASE): link = urljoin(base_url, match.group(1)) links.append(link) return links def _vault8_links(html: str, base_url: str) -> list[str]: """Extract Vault 8 source code repository links.""" links = [] for match in re.finditer(r'href=["\'](/vault8/[^"\']+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) return links def _cablegate_links(html: str, base_url: str) -> list[str]: """Extract Cablegate cable links with pagination support. Cablegate uses /plusd/cables/ with date-based navigation. Individual cables are at /plusd/cables/CABLE_ID """ links = [] # Individual cable links for match in re.finditer(r'href=["\'](/plusd/cables/[0-9A-Z]+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) # Pagination links for match in re.finditer(r'href=["\'](/plusd/[^"\']*(?:page|offset|start)=[^"\']+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) # Date-based navigation for match in re.finditer(r'href=["\'](/plusd/cables/\?[^"\']+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) return links def _spyfiles_links(html: str, base_url: str) -> list[str]: """Extract Spy Files document links.""" links = [] for match in re.finditer(r'href=["\'](/spyfiles/[^"\']+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) return links def _email_links(html: str, base_url: str) -> list[str]: """Extract email dump links with ID-based navigation.""" links = [] # Email ID links for match in re.finditer(r'href=["\']([^"\']*emailid/\d+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) # Pagination for match in re.finditer(r'href=["\']([^"\']*(?:page|offset)=\d+)["\']', html): link = urljoin(base_url, match.group(1)) links.append(link) return links def parse_cable_date(cable_id: str) -> Optional[str]: """Extract date from cable ID format. Cable IDs often start with a two-digit year and month: e.g., 07PARIS1234 → 2007 (approximate) """ match = re.match(r'^(\d{2})[A-Z]', cable_id) if match: year = int(match.group(1)) # WikiLeaks cables span roughly 1966-2010 if year >= 66: return f"19{year:02d}" else: return f"20{year:02d}" return None def detect_wikileaks_source(url: str) -> Optional[str]: """Detect which WikiLeaks collection a URL belongs to.""" if '/vault7/' in url: return 'vault7' elif '/vault8/' in url: return 'vault8' elif '/plusd/' in url or '/cablegate/' in url: return 'cablegate' elif '/spyfiles/' in url: return 'spyfiles' elif 'emailid/' in url or '/dnc-emails/' in url or '/podesta-emails/' in url: return 'emails' return None