Расширенный API Distribution
Distribution нужен, когда приложение самостоятельно управляет установкой, сессиями и профилями. Для обычных запросов достаточно HttpClient или AsyncHttpClient: они выполняют эту подготовку автоматически. Прямой выбор профиля не отменяет проверку его подписи и прав API-ключа.
Distribution(*, state_dir, channel=None, bundle=None) представляет постоянную установку. state_dir — её каталог; channel и bundle по умолчанию используют штатную поставку. Атрибуты directory, platform, channel, bundle, installation_file и state_file описывают размещение и платформу. store и materializer предоставляют доступ к хранилищу каталога и подготовке файлов; trust создаёт хранилище доверенных метаданных TUF при первом обращении. Для изменения установки используйте методы Distribution; прямое редактирование её хранилища не поддерживается. Для асинхронных сессий используются open_async() и async_session() того же объекта, отдельного класса AsyncDistribution нет.
| Вызов | Результат/назначение |
|---|---|
| prepare(now=None) | Заполняет пустое хранилище подписанным каталогом и файлами. Если файлы уже установлены, проверяет и возвращает действующий каталог. Не регистрирует установку и не загружает обновления поверх имеющегося содержимого; для обновления используйте update(). |
| enroll(api_key=...,now=None,auto_refresh=True) | Первичная регистрация новой установки; возвращает Engine. Не используйте для повторной регистрации уже существующей установки. |
| open(api_key=None,now=None,auto_refresh=False) | Открывает уже зарегистрированную установку и возвращает Engine. При ошибке не запускает новую регистрацию. |
| enroll_async / open_async | Обычные функции возвращают AsyncEngine; async with, без await перед фабрикой. |
| profile(engine,...) | Возвращает NativeProfile. Когда он больше не нужен, вызывающий код должен вызвать release(). Проще использовать session(), который освобождает профиль автоматически. |
| session(engine,...)/async_session(engine,...) | Контекстный менеджер Session/AsyncSession, автоматически владеющий профилем и освобождающий его. |
| navigation_context(url, *, selection=None) | Контекст открытия страницы. Без selection используется Chrome; передайте ProfileSelection выбранного профиля, чтобы получить соответствующий контекст Edge. |
| updater(engine,now=None) | Контекстный менеджер обновления; освобождает сетевые ресурсы при выходе. |
| update(engine,now=None) | Проверяет и устанавливает подписанное обновление. |
| recovery_updater(history_runtime_id=None) | Контекст координатора без открытого Engine для существующей истории. |
| recover(api_key=...,history_runtime_id=None,now=None) | Получает и проверяет актуальные данные для существующей установки с сохранением её идентификатора. Требует действующий API-ключ. |
| reconcile(history_runtime_id=None,now=None) | Определяет результат прерванного изменения установки и согласует её состояние, не меняя идентификатор. |
now у методов, которые его принимают, — целое число секунд Unix; None означает текущее время. Исключение: параметр Distribution.updater(now=...) в текущем коде не используется; время задаётся операции координатора либо через Distribution.update(now=...). Не подставляйте ложное время для обхода сроков. auto_refresh включает автоматическое продление разрешения на работу библиотеки. Новые профили этот параметр не загружает. Координатор updater() требует синхронный Engine, открытый этой же Distribution; операцию обновления завершите в отдельном синхронном контексте до открытия новой асинхронной сессии.
Параметры выбора профиля и сессии: browser='chrome', version=None, application='h2', profile_id=None, now=None. У сессии также есть owner_id=1, ca_bundle=None, proxy=None, cookie_policy=None.
Доступ к Pro-профилям определяется правами, полученными от сервиса лицензирования (engine.managed_info().paid_profiles). Явный profile_id не отменяет фильтры семейства, платформы, приложения и тарифа. Наибольшая подходящая версия выбирается, только если и version, и profile_id равны None; иначе применяется заданное ограничение. Реализацию транспорта определяет профиль, её не следует угадывать по номеру Chrome.
from duckonet import Distribution
async def fetch_existing(state_dir, url):
dist = Distribution(state_dir=state_dir)
async with dist.open_async() as engine:
async with dist.async_session(engine, application='h2') as session:
return await session.request('GET', url, context=dist.navigation_context(url))
Выбор профиля из каталога
ProfileQuery(*,family,platform,application,version=None,major_range=None,profile_id=None,tier=None,random=False). Все аргументы передаются по имени:
| Параметр | Значение |
|---|---|
| family | Семейство браузера: chrome или edge. |
| platform | windows или linux. |
| application | h1, h2, h3, h2_only, h3_only; их назначение описано в Client. Значение auto здесь не принимается. |
| version | Полная версия или None. Нельзя сочетать с major_range. |
| major_range | Включительный диапазон основных версий: кортеж двух целых чисел от 1 до 100000; начало не больше конца. |
| profile_id | Точный идентификатор или None. Остальные фильтры продолжают действовать. |
| tier | None, free или paid. В API значение paid соответствует Pro. |
| random | False по умолчанию. Без version, major_range и profile_id требуется True. Случайный выбор происходит один раз, а не перед каждым запросом. |
catalog_choices(store, now=..., native_id=..., query=...) возвращает кортеж подходящих записей без случайного выбора. Если их нет, возникает CATALOG_SELECTION_ABSENT. select_catalog_profile() возвращает ProfileSelection: обычный выбор требует ровно одну запись, случайный выбирает один индекс однократно.
verify_catalog_selection() проверяет неизменность области, поколения и списка и возвращает выбранный ProfileRecord; повторного выбора не делает. selection.profile — свойство. selection.describe() возвращает описание выбора. При открытии профиля подписи и права проверяются отдельно.
Ошибка CATALOG_SELECTION_ABSENT означает, что ни одна запись не соответствует всем фильтрам. При CATALOG_SELECTION_AMBIGUOUS уточните версию или ID либо явно разрешите случайный выбор. Если каталог изменился и выбор устарел, получите выбор заново из текущего каталога.
import time
from duckonet import Distribution, ProfileQuery, catalog_choices
def available_profiles(state_dir):
dist = Distribution(state_dir=state_dir)
with dist.open() as engine:
query = ProfileQuery(family='chrome', platform=dist.platform,
application='h2', tier='paid' if engine.managed_info().paid_profiles else 'free',
random=True)
rows = catalog_choices(dist.store, now=int(time.time()),
native_id=engine.catalog_native_id, query=query)
return [(row.id, row.version) for row in rows]
Прямая работа с профилем
Этот раздел нужен интеграциям, которые самостоятельно загружают и компилируют описание профиля. Для обычной сессии достаточно Distribution.session().
Для схемы 2 map_catalog_profile_v2(receipt, *, profile_id, native_id) преобразует структуру без подтверждения её происхождения. compile_selected_profile() требует authentication и trusted_application_keys; admission может быть None.
Для схемы 3 compile_selected_modular_profile() принимает program, profile_v7, signature, wire_policy, request_policy и использует существующий Engine. У AsyncEngine есть публичный одноимённый метод.
load_modular_profile_inputs(path) загружает описание сборки профиля и два закреплённых файла — подпись и каноническое представление. Проверяет схему, размеры, хеши и идентификаторы. inputs.as_kwargs() предназначен для компилятора профиля, не для HTTP-запроса. Подпись и права проверяет нативное ядро. Файлы должны соответствовать выбранному поколению и ID профиля.
Каталог схемы 1 можно читать и разбирать, но нельзя использовать для выполнения запросов: NativeMapping.require_executable() и compile_catalog_profile() отклоняют исполнение. Это номера форматов данных, а не версии публично выпускавшейся библиотеки. Distribution использует подписанные модульные профили схемы 3.
Разбор собственных настроек профиля
Fingerprint(*, name, program) и CustomProfileRegistry описывают собственные механические настройки без подтверждения браузерного соответствия. Они не заменяют подписанный профиль публичного выпуска. Аргументы Fingerprint передаются по имени; fingerprint.canonical_bytes и .sha256 — свойства.
registry.register(name, fingerprint) возвращает новый реестр, не меняя прежний. Пространство имён выбирается явно: builtin, json, fingerprint или custom. Библиотека не пытается угадать по строке путь к файлу или адрес загрузки.
from pathlib import Path
from duckonet import parse_fingerprint_json, CustomProfileRegistry, resolve_profile_input
def describe_custom(path):
fp = parse_fingerprint_json(Path(path).read_bytes())
registry = CustomProfileRegistry().register('local-example', fp)
value = resolve_profile_input('local-example', namespace='custom', registry=registry)
return value.describe()
Формат JSON: схема duckonet.fingerprint/1, поля assurance=caller_metadata_unverified, name и program. Предел — 262144 байта UTF-8. Неизвестные и повторные поля, числа с плавающей точкой и NaN запрещены. Реестр содержит до 128 записей без повторяющихся имён.
Пространство builtin содержит базовые настройки транспорта для низкоуровневых интеграций. Оно не является каталогом доступных браузеров. Чтобы выбрать поддерживаемую версию браузера для запросов, используйте browser и version клиента либо Distribution.profile().
ResolvedProfileInput.open_session() работает только с синхронным немодульным Engine и TransportConfig. Модульный Engine отклоняет такой профиль с PROFILE_INPUT_MODULAR_UNSIGNED. Пример выше только разбирает и описывает настройки: он не отправляет запрос и не устанавливает браузерный профиль. Для выполнения запросов через Distribution выберите подписанный профиль.
Настройка канала поставки
Для стандартного сервиса оставьте channel=None. Функция default_release_channel() возвращает настройки адресов и доверия, поставляемые с пакетом. Собственный канал задаётся объектом ReleaseChannel; все его аргументы именованные.
| Аргумент | Назначение |
|---|---|
| catalog_keys | Непустое отображение идентификаторов ключей в объекты Ed25519PublicKey для проверки каталога. |
| bootstrap | Байты исходного доверенного корневого документа TUF. |
| bootstrap_sha256 | SHA256 этих байтов; несовпадение отклоняется. |
| repository_base | Базовый адрес репозитория подписанных метаданных. |
| artifact_base | Базовый адрес файлов компонентов. |
| pairing_origin | Адрес сервиса регистрации; по умолчанию https://duckolab.com. |
| permission_origin | Адрес сервиса прав; по умолчанию https://api.duckolab.com. |
| available | True по умолчанию. При False начальная подготовка использует файлы из bundle; это не отключает проверку подписей и не предоставляет доступ без регистрации. |
Доверенные ключи и исходный корневой документ должны приходить из вашего доверенного канала поставки. Не подставляйте данные произвольного сервера вместо доверенных настроек только ради устранения ошибки проверки.
Ошибки первого запуска и восстановление
При прерывании первой регистрации создайте новый клиент с тем же API-ключом и state_dir. Библиотека продолжит сохранённую операцию, в том числе если подтверждение успело истечь. Сбрасывать каталог установки не нужно.
После временной сетевой ошибки повторите запуск клиента с прежним state_dir. Не удаляйте каталог и не вызывайте enroll() после неудачного open(). Если доступ отозван или права истекли, сначала восстановите действующие права ключа. Повреждённое состояние не исправляется повторной регистрацией. recover() сохраняет идентификатор установки и защиту от отката; reconcile() завершает разбор операции с неопределённым результатом записи. Обе функции проверяют подписи, время и права и могут отказать.
Повтор запуска библиотеки и повтор HTTP-запроса — разные действия. Не повторяйте POST бесконечно при неизвестном результате: целевой сервер мог уже выполнить операцию.
Обновление между прогонами
Distribution.profile() выбирает профиль по явным фильтрам и не запускает цикл переключения прогонов. Для version="last" используйте HttpClient/AsyncHttpClient и их run(), как описано в руководстве обновлений.