Spec-Zone.ru › Python 3.12

email.message: Представление электронного сообщения

Исходный код: Lib/email/message.py

Добавлена в версии 3.6: [1]

Основной класс в пакете email — это класс EmailMessage, импортируемый из модуля email.message. Он является базовым классом для email модели объектов. EmailMessage предоставляет основные возможности для установки и запроса заголовков, доступа к телам сообщений и создания или изменения структурированных сообщений.

Электронное сообщение состоит из заголовков и данных (которые также называются содержанием). Заголовки — это имена и значения полей в стиле RFC 5322 или RFC 6532, где имя и значение поля разделены двоеточием. Двоеточие не является частью имени или значения поля. Данные могут быть простым текстовым сообщением, двоичным объектом или структурированной последовательностью вложенных сообщений, каждое со своим набором заголовков и своими данными. Последний тип данных указывается типом MIME сообщения, например multipart/* или message/rfc822.

Концептуальная модель, предоставляемая объектом EmailMessage, представляет собой упорядоченный словарь заголовков, связанный с данными, которые представляют тело сообщения в формате RFC 5322, который может быть списком вложенных EmailMessage объектов. В дополнение к обычным методам словаря для доступа к именам и значениям заголовков существуют методы для доступа к специализированной информации из заголовков (например, MIME-тип контента), для работы с данными, для генерации сериализованной версии сообщения и для рекурсивного обхода древовидной структуры объекта.

Интерфейс EmailMessage по типу словаря индексируется именами заголовков, которые должны быть значениями ASCII. Значения словаря — это строки с дополнительными методами. Заголовки хранятся и возвращаются в сохраняющей регистр форме, но имена полей сопоставляются без учета регистра. Ключи упорядочены, но в отличие от обычного словаря, могут быть дубликатами. Предоставляются дополнительные методы для работы с заголовками, имеющими дублирующие ключи.

Данные — это либо строка или байтовый объект в случае простых сообщений, либо список объектов EmailMessage в случае документов MIME-контейнеров, таких как multipart/* и message/rfc822.

class email.message.EmailMessage(policy=default)

Если policy указан, используйте правила, которые он задаёт, для обновления и сериализации представления сообщения. Если policy не задан, используйте политику default, которая следует правилам RFC для электронной почты, за исключением конечных строк (вместо предписанных RFC \r\n, она использует стандартные Python \n конечные строки). Для получения дополнительной информации см. policy документацию.

as_string(unixfrom=False, maxheaderlen=None, policy=None)

Возвращает всё сообщение, сглаженное в виде строки. Когда необязательный параметр unixfrom равен true, заголовок конверта включён в возвращаемую строку. unixfrom по умолчанию равен False. Для обратной совместимости с базовым классом Message принимается maxheaderlen, но по умолчанию равен None, что означает, что длина строки по умолчанию контролируется параметром max_line_length политики. Аргумент policy может быть использован для переопределения политики, полученной из экземпляра сообщения. Это можно использовать для управления некоторым форматированием, создаваемым методом, так как указанная policy будет передана в Generator.

Сглаживание сообщения может вызвать изменения в EmailMessage, если для завершения преобразования в строку необходимо заполнить значения по умолчанию (например, могут быть сгенерированы или изменены границы MIME).

Обратите внимание, что этот метод предоставляется для удобства и может не быть наиболее подходящим способом сериализации сообщений в вашем приложении, особенно если вы работаете с несколькими сообщениями. См. email.generator.Generator для более гибкого API для сериализации сообщений. Также обратите внимание, что этот метод ограничен созданием сериализованных сообщений, как «чистые 7 бит», когда utf8 равен False, что является значением по умолчанию.

Изменено в версии 3.6: поведение по умолчанию, когда maxheaderlen не указан, было изменено с присвоения значения по умолчанию 0 на присвоение значения max_line_length из политики.

__str__()

Эквивалентно as_string(policy=self.policy.clone(utf8=True)). Позволяет str(msg) генерировать строку, содержащую сериализованное сообщение в удобочитаемом формате.

Изменено в версии 3.4: метод был изменён на использование utf8=True, тем самым создавая представление сообщения в формате, подобном RFC 6531, а не являясь прямым псевдонимом для as_string().

as_bytes(unixfrom=False, policy=None)

Возвращает всё сообщение, сглаженное в виде объекта bytes. Когда необязательный параметр unixfrom равен true, заголовок конверта включён в возвращаемую строку. unixfrom по умолчанию равен False. Аргумент policy может быть использован для переопределения политики, полученной из экземпляра сообщения. Это можно использовать для управления некоторым форматированием, создаваемым методом, так как указанная policy будет передана в BytesGenerator.

Сглаживание сообщения может вызвать изменения в EmailMessage, если для завершения преобразования в строку необходимо заполнить значения по умолчанию (например, могут быть сгенерированы или изменены границы MIME).

Обратите внимание, что этот метод предоставляется для удобства и может не быть наиболее подходящим способом сериализации сообщений в вашем приложении, особенно если вы работаете с несколькими сообщениями. См. email.generator.BytesGenerator для более гибкого API для сериализации сообщений.

__bytes__()

Эквивалентно as_bytes(). Позволяет bytes(msg) генерировать объект bytes, содержащий сериализованное сообщение.

is_multipart()

Возвращает True, если содержимое сообщения является списком дочерних объектов EmailMessage, в противном случае возвращает False. Когда is_multipart() возвращает False, содержимое должно быть объектом типа строка (который может быть бинарным содержимым, закодированным CTE). Обратите внимание, что is_multipart(), возвращающий True, не обязательно означает, что «msg.get_content_maintype() == ‘multipart’” вернёт True. Например, is_multipart вернёт True, когда EmailMessage имеет тип message/rfc822.

set_unixfrom(unixfrom)

Устанавливает заголовок конверта сообщения на unixfrom, который должен быть строкой. (См. mboxMessage для краткого описания этого заголовка.)

get_unixfrom()

Возвращает заголовок конверта сообщения. По умолчанию равен None, если заголовок конверта никогда не был установлен.

Следующие методы реализуют интерфейс, подобный отображению, для доступа к заголовкам сообщения. Обратите внимание, что между этими методами и обычным интерфейсом отображения (например, словарем) существуют некоторые семантические различия. Например, в словаре нет дублирующих ключей, но здесь могут быть дублирующиеся заголовки сообщений. Кроме того, в словарях нет гарантии порядка ключей, возвращаемых keys(), но в объекте EmailMessage заголовки всегда возвращаются в порядке их появления в исходном сообщении или в порядке, в котором они были добавлены в сообщение позже. Любой заголовок, удалённый и затем повторно добавленный, всегда добавляется в конец списка заголовков.

Эти семантические различия являются намеренными и ориентированы на удобство в наиболее распространённых случаях использования.

Обратите внимание, что во всех случаях любой заголовок конверта, присутствующий в сообщении, не включён в интерфейс отображения.

__len__()

Возвращает общее количество заголовков, включая дубликаты.

__contains__(name)

Возвращает True, если объект сообщения имеет поле с именем name. Сопоставление выполняется без учёта регистра, и name не включает в себя заключительный двоеточие. Используется для оператора in. Например:

if 'message-id' in myMessage:
   print('Message-ID:', myMessage['message-id'])
__getitem__(name)

Возвращает значение поля имени заголовка. name не включает в себя разделитель двоеточия. Если заголовок отсутствует, возвращается None; исключение KeyError никогда не генерируется.

Обратите внимание, что если имя поля появляется более одного раза в заголовках сообщения, то ровно то значение поля, которое будет возвращено, не определено. Используйте метод get_all(), чтобы получить значения всех существующих заголовков с именем name.

Используя стандартные (не-compat32) политики, возвращаемое значение является экземпляром подкласса email.headerregistry.BaseHeader.

__setitem__(name, val)

Добавьте заголовок к сообщению с именем поля name и значением val. Поле добавляется в конец существующих заголовков сообщения.

Обратите внимание, что это не перезаписывает или удаляет любой существующий заголовок с тем же именем. Если вы хотите убедиться, что новый заголовок является единственным в сообщении с именем поля name, удалите поле сначала, например:

del msg['subject']
msg['subject'] = 'Python roolz!'

Если policy определяет уникальность некоторых заголовков (как стандартные политики), этот метод может вызвать ValueError при попытке присвоить значение такому заголовку, когда он уже существует. Это поведение сделано умышленно ради согласованности, но не полагайтесь на него, так как мы можем в будущем изменить такое поведение, чтобы такие присваивания автоматически удаляли существующий заголовок.

__delitem__(name)

Удалите все вхождения поля с именем name из заголовков сообщения. Исключение не возникает, если указанное поле отсутствует в заголовках.

keys()

Возвращает список всех имён полей заголовков сообщения.

values()

Возвращает список всех значений полей заголовков сообщения.

items()

Возвращает список кортежей из 2 элементов, содержащих все заголовки и значения полей сообщения.

get(name, failobj=None)

Возвращает значение поля заголовка с заданным именем. Это идентично __getitem__(), за исключением того, что необязательный параметр failobj возвращается, если заголовок с заданным именем отсутствует (failobj по умолчанию равен None).

Вот несколько дополнительных полезных методов, связанных с заголовками:

get_all(name, failobj=None)

Возвращает список всех значений для поля с именем name. Если в сообщении нет заголовков с таким именем, возвращается failobj (по умолчанию None).

add_header(_name, _value, **_params)

Расширенная установка заголовков. Этот метод похож на __setitem__(), за исключением того, что можно передавать дополнительные параметры заголовка в виде ключевых аргументов. _name — это поле заголовка для добавления, а _value — основное значение заголовка.

Для каждого элемента в словаре ключевых аргументов _params, ключ используется в качестве имени параметра, при этом подчеркивания заменяются на дефисы (поскольку дефисы запрещены в идентификаторах Python). Обычно параметр добавляется как key="value", если значение не равно None, в противном случае добавляется только ключ.

Если значение содержит не-ASCII символы, кодировка и язык могут быть явно заданы, указав значение в виде тройки кортежей в формате (CHARSET, LANGUAGE, VALUE), где CHARSET — строка, называющая кодировку, которая будет использоваться для кодирования значения, LANGUAGE обычно может быть установлена на None или пустую строку (см. RFC 2231 для других возможностей), и VALUE — строковое значение, содержащее не-ASCII символы. Если тройка кортежей не передана, и значение содержит не-ASCII символы, оно автоматически кодируется в формате RFC 2231 с использованием CHARSET utf-8 и LANGUAGE None.

Вот пример:

msg.add_header('Content-Disposition', 'attachment', filename='bud.gif')

Это добавит заголовок, который выглядит как

Content-Disposition: attachment; filename="bud.gif"

Пример расширенного интерфейса с не-ASCII символами:

msg.add_header('Content-Disposition', 'attachment',
               filename=('iso-8859-1', '', 'Fußballer.ppt'))
replace_header(_name, _value)

Заменить заголовок. Заменить первый найденный заголовок в сообщении, соответствующий _name, сохраняя порядок заголовков и регистр имени поля исходного заголовка. Если соответствующий заголовок не найден, вызывается KeyError.

get_content_type()

Возвращает тип контента сообщения, приведённый к нижнему регистру в формате maintype/subtype. Если в сообщении нет заголовка Content-Type, возвращается значение, возвращаемое get_default_type(). Если заголовок Content-Type некорректен, возвращается text/plain.

(Согласно RFC 2045, у сообщений всегда есть тип по умолчанию, get_content_type() всегда возвращает значение. RFC 2045 определяет тип сообщения по умолчанию как text/plain, если он не находится внутри контейнера multipart/digest, в этом случае он будет message/rfc822. Если у заголовка Content-Type есть некорректное указание типа, RFC 2045 предписывает использование типа по умолчанию text/plain.)

get_content_maintype()

Возвращает основную часть типа контента сообщения. Это часть maintype строки, возвращаемой get_content_type().

get_content_subtype()

Возвращает подтип контента сообщения. Это часть subtype строки, возвращаемой get_content_type().

get_default_type()

Возвращает тип контента по умолчанию. У большинства сообщений тип контента по умолчанию — text/plain, за исключением сообщений, являющихся подчастями контейнеров multipart/digest. Такие подчасти имеют тип контента по умолчанию message/rfc822.

set_default_type(ctype)

Устанавливает тип контента по умолчанию. ctype должен быть либо text/plain, либо message/rfc822, хотя это и не проверяется. Тип контента по умолчанию не хранится в заголовке Content-Type, поэтому он влияет только на возвращаемое значение методов get_content_type, когда в сообщении нет заголовка Content-Type.

set_param(param, value, header='Content-Type', requote=True, charset=None, language='', replace=False)

Устанавливает параметр в заголовке Content-Type. Если параметр уже существует в заголовке, его значение заменяется на value. Когда header равен Content-Type (по умолчанию) и заголовок ещё не существует в сообщении, он добавляется, его значение устанавливается в text/plain, и добавляется новое значение параметра. Необязательный параметр header задаёт альтернативный заголовок для Content-Type.

Если значение содержит не-ASCII символы, кодировка и язык могут быть явно заданы с помощью необязательных параметров charset и language. Необязательный параметр language задаёт язык RFC 2231, по умолчанию пустая строка. И charset, и language должны быть строками. По умолчанию используется utf8 charset и None для language.

Если replace равно False (по умолчанию), заголовок перемещается в конец списка заголовков. Если replace равно True, заголовок будет обновлён на месте.

Использование параметра requote с объектами EmailMessage устарело.

Обратите внимание, что к существующим значениям параметров заголовков можно получить доступ через атрибут params значения заголовка (например, msg['Content-Type'].params['charset']).

Изменено в версии 3.4: replace keyword был добавлен.

del_param(param, header='content-type', requote=True)

Удалите заданный параметр полностью из заголовка Content-Type. Заголовок будет переписан на месте без параметра или его значения. Необязательный параметр header указывает альтернативу Content-Type.

Использование параметра requote с объектами EmailMessage устарело.

get_filename(failobj=None)

Возвращает значение параметра filename заголовка Content-Disposition сообщения. Если заголовок не содержит параметр filename, этот метод возвращается к поиску параметра name в заголовке Content-Type. Если ни один из них не найден или заголовок отсутствует, возвращается failobj. Возвращаемая строка всегда будет без кавычек, как в email.utils.unquote().

get_boundary(failobj=None)

Возвращает значение параметра boundary заголовка Content-Type сообщения или failobj, если заголовок отсутствует или не имеет параметра boundary. Возвращаемая строка всегда будет без кавычек, как в email.utils.unquote().

set_boundary(boundary)

Устанавливает параметр boundary заголовка Content-Type в значение boundary. set_boundary() всегда приводит boundary к кавычному виду, если необходимо. Если у объекта сообщения нет заголовка Content-Type, возникает ошибка HeaderParseError.

Обратите внимание, что использование этого метода отличается от удаления старого заголовка Content-Type и добавления нового с новым значением boundary через add_header(), потому что set_boundary() сохраняет порядок заголовка Content-Type в списке заголовков.

get_content_charset(failobj=None)

Возвращает параметр charset заголовка Content-Type, преобразованный к нижнему регистру. Если заголовка Content-Type нет или у него нет параметра charset, возвращается failobj.

get_charsets(failobj=None)

Возвращает список, содержащий имена наборов символов в сообщении. Если сообщение является multipart, то список будет содержать по одному элементу для каждого подэлемента в полезной нагрузке, иначе список будет длиной 1.

Каждый элемент в списке будет строкой, которая является значением параметра charset в заголовке Content-Type для соответствующего подэлемента. Если у подэлемента нет заголовка Content-Type, параметра charset или он не относится к основному MIME-типу text, то соответствующий элемент в возвращаемом списке будет равен failobj.

is_attachment()

Возвращает True, если есть заголовок Content-Disposition и его (независимое от регистра) значение равно attachment, False в противном случае.

Изменено в версии 3.4.2: is_attachment теперь является методом, а не свойством, для согласованности с is_multipart().

get_content_disposition()

Возвращает значение заголовка Content-Disposition сообщения в нижнем регистре (без параметров), если он существует, или None. Возможные значения для этого метода — inline, attachment или None, если сообщение соответствует RFC 2183.

Добавлена в версии 3.5.

Следующие методы относятся к запросу и манипулированию содержимым (полезной нагрузкой) сообщения.

walk()

Метод walk() — это универсальный генератор, который можно использовать для итерации по всем частям и подчастям дерева объекта сообщения в порядке обхода в глубину. Обычно вы будете использовать walk() в качестве итератора в цикле for; на каждой итерации возвращается следующий подэлемент.

Вот пример, который выводит тип MIME каждой части структуры multipart-сообщения:

>>> for part in msg.walk():
...     print(part.get_content_type())
multipart/report
text/plain
message/delivery-status
text/plain
text/plain
message/rfc822
text/plain

walk перебирает подчасти любой части, где is_multipart() возвращает True, даже если msg.get_content_maintype() == 'multipart' может вернуть False. Мы можем увидеть это в нашем примере, используя вспомогательную функцию отладки _structure:

>>> from email.iterators import _structure
>>> for part in msg.walk():
...     print(part.get_content_maintype() == 'multipart',
...           part.is_multipart())
True True
False False
False True
False False
False False
False True
False False
>>> _structure(msg)
multipart/report
    text/plain
    message/delivery-status
        text/plain
        text/plain
    message/rfc822
        text/plain

Здесь части message не являются multiparts, но они содержат подчасти. is_multipart() возвращает True и walk спускается в подчасти.

get_body(preferencelist=('related', 'html', 'plain'))

Возвращает MIME-часть, которая является лучшим кандидатом на роль «тела» сообщения.

preferencelist должен быть последовательностью строк из набора related, html и plain и указывает порядок предпочтения типа содержимого возвращаемой части.

Начните поиск соответствующих кандидатов с объекта, на котором вызывается метод get_body.

Если related не включен в preferencelist, рассмотрите корневую часть (или подчасть корневой части) любой связанной части в качестве кандидата, если (под-)часть соответствует предпочтению.

При обнаружении multipart/related проверьте параметр start, и если найдена часть с соответствующим заголовком Content-ID, рассмотрите только ее при поиске соответствующих кандидатов. В противном случае рассмотрите только первую (по умолчанию, корневую) часть multipart/related.

Если ни один из кандидатов не соответствует ни одному из предпочтений в preferencelist, верните None.

Примечания: (1) Для большинства приложений единственные комбинации preferencelist, которые действительно имеют смысл, это ('plain',), ('html', 'plain') и стандартное значение ('related', 'html', 'plain'). (2) Поскольку сопоставление начинается с объекта, на котором вызывается get_body, вызов get_body на multipart/related вернет сам объект, если preferencelist не имеет значения по умолчанию. (3) Сообщения (или части сообщений), которые не указывают заголовок Content-Type или заголовок Content-Type которого недействителен, будут обрабатываться как имеющие тип text/plain, что может иногда привести к тому, что get_body вернет неожиданные результаты.

iter_attachments()

Возвращает итератор по всем непосредственным подчастям сообщения, которые не являются частями «тела» сообщения. То есть пропустите первое вхождение каждой из text/plain, text/html, multipart/related или multipart/alternative (если они не отмечены явно как вложения с помощью Content-Disposition: attachment) и верните все остальные части. При применении непосредственно к multipart/related, верните итератор по всем связанным частям, за исключением корневой части (т. е. части, на которую указывает параметр start, или первой части, если параметр start или параметр start не соответствует Content-ID ни одной из частей). При применении непосредственно к multipart/alternative или не-multipart, верните пустой итератор.

iter_parts()

Возвращает итератор по всем непосредственным подчастям сообщения, который будет пустым для не-multipart. (См. также walk().)

get_content(*args, content_manager=None, **kw)

Вызовите метод get_content() объекта content_manager, передав self в качестве объекта сообщения и все другие аргументы или ключевые слова в качестве дополнительных аргументов. Если content_manager не указан, используйте content_manager, заданный текущим policy.

set_content(*args, content_manager=None, **kw)

Вызовите метод set_content() объекта content_manager, передав self в качестве объекта сообщения и все другие аргументы или ключевые слова в качестве дополнительных аргументов. Если content_manager не указан, используйте content_manager, заданный текущим policy.

make_related(boundary=None)

Преобразуйте сообщение, которое не является multipart, в сообщение multipart/related, переместив все существующие заголовки Content- и полезную нагрузку в первую (новую) часть multipart. Если задан параметр boundary, используйте его как строку разделителя в multipart, в противном случае оставьте разделитель для автоматического создания при необходимости (например, при сериализации сообщения).

make_alternative(boundary=None)

Преобразуйте сообщение, которое не является multipart или multipart/related, в сообщение multipart/alternative, переместив все существующие заголовки Content- и полезную нагрузку в первую (новую) часть multipart. Если задан параметр boundary, используйте его как строку разделителя в multipart, в противном случае оставьте разделитель для автоматического создания при необходимости (например, при сериализации сообщения).

make_mixed(boundary=None)

Преобразуйте сообщение, которое не является multipart, multipart/related или multipart-alternative, в сообщение multipart/mixed, переместив все существующие заголовки Content- и полезную нагрузку в первую (новую) часть multipart. Если задан параметр boundary, используйте его как строку разделителя в multipart, в противном случае оставьте разделитель для автоматического создания при необходимости (например, при сериализации сообщения).

add_related(*args, content_manager=None, **kw)

Если сообщение является multipart/related, создайте новый объект сообщения, передайте все аргументы в его метод set_content() и attach() его в multipart. Если сообщение не является multipart, вызовите make_related(), а затем выполните действия, как описано выше. Если сообщение является другим типом multipart, вызовите исключение TypeError. Если content_manager не указан, используйте content_manager, заданный текущим policy. Если добавленная часть не имеет заголовка Content-Disposition, добавьте заголовок со значением inline.

add_alternative(*args, content_manager=None, **kw)

Если сообщение является multipart/alternative, создайте новый объект сообщения, передайте все аргументы в его метод set_content() и attach() его в multipart. Если сообщение не является multipart или multipart/related, вызовите make_alternative(), а затем выполните действия, как описано выше. Если сообщение является другим типом multipart, вызовите исключение TypeError. Если content_manager не указан, используйте content_manager, заданный текущим policy.

add_attachment(*args, content_manager=None, **kw)

Если сообщение является multipart/mixed, создайте новый объект сообщения, передайте все аргументы в его метод set_content() и attach() его в multipart. Если сообщение не является multipart, multipart/related или multipart/alternative, вызовите make_mixed(), а затем выполните действия, как описано выше. Если content_manager не указан, используйте content_manager, заданный текущим policy. Если у добавленной части нет заголовка Content-Disposition, добавьте заголовок со значением attachment. Этот метод может использоваться как для явных вложений (Content-Disposition: attachment), так и для вложений по умолчанию (Content-Disposition: inline), передав соответствующие параметры в content_manager.

clear()

Удалите полезную нагрузку и все заголовки.

clear_content()

Удалите полезную нагрузку и все заголовки !Content-, оставив все остальные заголовки нетронутыми в исходном порядке.

EmailMessage объекты имеют следующие атрибуты экземпляров:

preamble

Формат MIME-документа допускает некотоый текст между пустой строкой после заголовков и первой строкой разделителя multipart. Обычно этот текст не виден в программе для чтения почты, поддерживающей MIME, потому что он находится вне стандартного MIME-бронирования. Однако при просмотре исходного текста сообщения или при просмотре сообщения в программе для чтения почты, не поддерживающей MIME, этот текст может стать видимым.

Атрибут preamble содержит этот ведущий дополнительный текст для MIME-документов. При обнаружении Parser некоторого текста после заголовков, но перед первой строкой разделителя, он присваивает этот текст атрибуту preamble сообщения. При записи Generator текстового представления MIME-сообщения и обнаружении у сообщения атрибута preamble, он запишет этот текст в область между заголовками и первым разделителем. Подробности см. в email.parser и email.generator.

Обратите внимание, что если у объекта сообщения нет преамбулы, атрибут preamble будет None.

epilogue

Атрибут epilogue действует аналогично атрибуту preamble, за исключением того, что он содержит текст, который появляется между последним разделителем и концом сообщения. Как и в случае с preamble, если нет текста эпилога, этот атрибут будет None.

defects

Атрибут defects содержит список всех проблем, обнаруженных при разборе этого сообщения. Подробное описание возможных дефектов разбора см. в email.errors.

class email.message.MIMEPart(policy=default)

Этот класс представляет собой часть MIME-сообщения. Он идентичен EmailMessage, за исключением того, что при вызове set_content() не добавляются заголовки MIME-Version, поскольку подчасти не нуждаются в собственных заголовках MIME-Version.

Примечания

[1]

Изначально добавлен в версии 3.4 как пробный модуль. Документация по классу сообщения устаревшей версии перенесена в email.message.Message: Представление электронного сообщения с использованием API compat32.

© 2001–2024 Python Software Foundation
Licensed under the PSF License.
https://docs.python.org/3.12/library/email.message.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API