mirror of
https://github.com/LonamiWebs/Telethon.git
synced 2025-08-02 11:10:18 +03:00
Merge branch 'master' into asyncio
This commit is contained in:
commit
cb75092ba1
|
@ -11,10 +11,10 @@ Accessing the Full API
|
|||
reason not to, like a method not existing or you wanting more control.
|
||||
|
||||
|
||||
The ``TelegramClient`` doesn't offer a method for every single request
|
||||
the Telegram API supports. However, it's very simple to *call* or *invoke*
|
||||
any request. Whenever you need something, don't forget to `check the
|
||||
documentation`__ and look for the `method you need`__. There you can go
|
||||
The `telethon.telegram_client.TelegramClient` doesn't offer a method for every
|
||||
single request the Telegram API supports. However, it's very simple to *call*
|
||||
or *invoke* any request. Whenever you need something, don't forget to `check
|
||||
the documentation`__ and look for the `method you need`__. There you can go
|
||||
through a sorted list of everything you can do.
|
||||
|
||||
|
||||
|
@ -30,9 +30,9 @@ You should also refer to the documentation to see what the objects
|
|||
(constructors) Telegram returns look like. Every constructor inherits
|
||||
from a common type, and that's the reason for this distinction.
|
||||
|
||||
Say ``client.send_message()`` didn't exist, we could use the `search`__
|
||||
to look for "message". There we would find :tl:`SendMessageRequest`,
|
||||
which we can work with.
|
||||
Say `telethon.telegram_client.TelegramClient.send_message` didn't exist,
|
||||
we could use the `search`__ to look for "message". There we would find
|
||||
:tl:`SendMessageRequest`, which we can work with.
|
||||
|
||||
Every request is a Python class, and has the parameters needed for you
|
||||
to invoke it. You can also call ``help(request)`` for information on
|
||||
|
@ -63,7 +63,7 @@ construct one, for instance:
|
|||
|
||||
peer = InputPeerUser(user_id, user_hash)
|
||||
|
||||
Or we call ``.get_input_entity()``:
|
||||
Or we call `telethon.telegram_client.TelegramClient.get_input_entity()`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -74,7 +74,7 @@ When you're going to invoke an API method, most require you to pass an
|
|||
``.get_input_entity()`` is more straightforward (and often
|
||||
immediate, if you've seen the user before, know their ID, etc.).
|
||||
If you also need to have information about the whole user, use
|
||||
``.get_entity()`` instead:
|
||||
`telethon.telegram_client.TelegramClient.get_entity()` instead:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
@ -83,7 +83,7 @@ If you also need to have information about the whole user, use
|
|||
In the later case, when you use the entity, the library will cast it to
|
||||
its "input" version for you. If you already have the complete user and
|
||||
want to cache its input version so the library doesn't have to do this
|
||||
every time its used, simply call ``.get_input_peer``:
|
||||
every time its used, simply call `telethon.utils.get_input_peer`:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ Session Files
|
|||
The first parameter you pass to the constructor of the ``TelegramClient`` is
|
||||
the ``session``, and defaults to be the session name (or full path). That is,
|
||||
if you create a ``TelegramClient('anon')`` instance and connect, an
|
||||
``anon.session`` file will be created on the working directory.
|
||||
``anon.session`` file will be created in the working directory.
|
||||
|
||||
Note that if you pass a string it will be a file in the current working
|
||||
directory, although you can also pass absolute paths.
|
||||
|
|
|
@ -26,7 +26,7 @@ That's it! This is the old way to listen for raw updates, with no further
|
|||
processing. If this feels annoying for you, remember that you can always
|
||||
use :ref:`working-with-updates` but maybe use this for some other cases.
|
||||
|
||||
Now let's do something more interesting. Every time an user talks to use,
|
||||
Now let's do something more interesting. Every time an user talks to us,
|
||||
let's reply to them with the same text reversed:
|
||||
|
||||
.. code-block:: python
|
||||
|
|
|
@ -58,8 +58,7 @@ Manual Installation
|
|||
|
||||
5. Done!
|
||||
|
||||
To generate the `method documentation`__, ``cd docs`` and then
|
||||
``python3 generate.py`` (if some pages render bad do it twice).
|
||||
To generate the `method documentation`__, ``python3 setup.py gen docs``.
|
||||
|
||||
|
||||
Optional dependencies
|
||||
|
|
|
@ -2,14 +2,15 @@
|
|||
Deleted, Limited or Deactivated Accounts
|
||||
========================================
|
||||
|
||||
If you're from Iran or Russian, we have bad news for you.
|
||||
Telegram is much more likely to ban these numbers,
|
||||
as they are often used to spam other accounts,
|
||||
likely through the use of libraries like this one.
|
||||
The best advice we can give you is to not abuse the API,
|
||||
like calling many requests really quickly,
|
||||
If you're from Iran or Russia, we have bad news for you. Telegram is much more
|
||||
likely to ban these numbers, as they are often used to spam other accounts,
|
||||
likely through the use of libraries like this one. The best advice we can
|
||||
give you is to not abuse the API, like calling many requests really quickly,
|
||||
and to sign up with these phones through an official application.
|
||||
|
||||
We have also had reports from Kazakhstan and China, where connecting
|
||||
would fail. To solve these connection problems, you should use a proxy.
|
||||
|
||||
Telegram may also ban virtual (VoIP) phone numbers,
|
||||
as again, they're likely to be used for spam.
|
||||
|
||||
|
|
|
@ -29,5 +29,12 @@ class MessageDeleted(EventBuilder):
|
|||
super().__init__(
|
||||
chat_peer=peer, msg_id=(deleted_ids or [0])[0]
|
||||
)
|
||||
if peer is None:
|
||||
# If it's not a channel ID, then it was private/small group.
|
||||
# We can't know which one was exactly unless we logged all
|
||||
# messages, but we can indicate that it was maybe either of
|
||||
# both by setting them both to True.
|
||||
self.is_private = self.is_group = True
|
||||
|
||||
self.deleted_id = None if not deleted_ids else deleted_ids[0]
|
||||
self.deleted_ids = deleted_ids
|
||||
|
|
|
@ -28,8 +28,16 @@ class NewMessage(EventBuilder):
|
|||
"""
|
||||
def __init__(self, incoming=None, outgoing=None,
|
||||
chats=None, blacklist_chats=False, pattern=None):
|
||||
if incoming is not None and outgoing is None:
|
||||
outgoing = not incoming
|
||||
elif outgoing is not None and incoming is None:
|
||||
incoming = not incoming
|
||||
|
||||
if incoming and outgoing:
|
||||
raise ValueError('Can only set either incoming or outgoing')
|
||||
self.incoming = self.outgoing = None # Same as no filter
|
||||
elif all(x is not None and not x for x in (incoming, outgoing)):
|
||||
raise ValueError("Don't create an event handler if you "
|
||||
"don't want neither incoming or outgoing!")
|
||||
|
||||
super().__init__(chats=chats, blacklist_chats=blacklist_chats)
|
||||
self.incoming = incoming
|
||||
|
|
|
@ -11,8 +11,7 @@ from ..tl.types import (
|
|||
ServerDHInnerData, ClientDHInnerData, DhGenOk, DhGenRetry, DhGenFail
|
||||
)
|
||||
from .. import helpers as utils
|
||||
from ..crypto import AES, AuthKey, Factorization
|
||||
from ..crypto import rsa
|
||||
from ..crypto import AES, AuthKey, Factorization, rsa
|
||||
from ..errors import SecurityError
|
||||
from ..extensions import BinaryReader
|
||||
from ..network import MtProtoPlainSender
|
||||
|
|
|
@ -963,7 +963,7 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
Raises:
|
||||
``MessageAuthorRequiredError`` if you're not the author of the
|
||||
message but try editing it anyway.
|
||||
message but tried editing it anyway.
|
||||
|
||||
``MessageNotModifiedError`` if the contents of the message were
|
||||
not modified at all.
|
||||
|
@ -1031,7 +1031,8 @@ class TelegramClient(TelegramBareClient):
|
|||
async def iter_messages(self, entity, limit=20, offset_date=None,
|
||||
offset_id=0, max_id=0, min_id=0, add_offset=0,
|
||||
search=None, filter=None, from_user=None,
|
||||
batch_size=100, wait_time=None, _total=None):
|
||||
batch_size=100, wait_time=None, ids=None,
|
||||
_total=None):
|
||||
"""
|
||||
Iterator over the message history for the specified entity.
|
||||
|
||||
|
@ -1059,7 +1060,7 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
max_id (`int`):
|
||||
All the messages with a higher (newer) ID or equal to this will
|
||||
be excluded
|
||||
be excluded.
|
||||
|
||||
min_id (`int`):
|
||||
All the messages with a lower (older) ID or equal to this will
|
||||
|
@ -1091,6 +1092,15 @@ class TelegramClient(TelegramBareClient):
|
|||
If left to ``None``, it will default to 1 second only if
|
||||
the limit is higher than 3000.
|
||||
|
||||
ids (`int`, `list`):
|
||||
A single integer ID (or several IDs) for the message that
|
||||
should be returned. This parameter takes precedence over
|
||||
the rest (which will be ignored if this is set). This can
|
||||
for instance be used to get the message with ID 123 from
|
||||
a channel. Note that if the message doesn't exist, ``None``
|
||||
will appear in its place, so that zipping the list of IDs
|
||||
with the messages can match one-to-one.
|
||||
|
||||
_total (`list`, optional):
|
||||
A single-item list to pass the total parameter by reference.
|
||||
|
||||
|
@ -1110,6 +1120,23 @@ class TelegramClient(TelegramBareClient):
|
|||
you think may be good.
|
||||
"""
|
||||
entity = await self.get_input_entity(entity)
|
||||
if ids:
|
||||
if not utils.is_list_like(ids):
|
||||
ids = (ids,)
|
||||
async for x in self._iter_ids(entity, ids, total=_total):
|
||||
await yield_(x)
|
||||
return
|
||||
|
||||
# Telegram doesn't like min_id/max_id. If these IDs are low enough
|
||||
# (starting from last_id - 100), the request will return nothing.
|
||||
#
|
||||
# We can emulate their behaviour locally by setting offset = max_id
|
||||
# and simply stopping once we hit a message with ID <= min_id.
|
||||
offset_id = max(offset_id, max_id)
|
||||
if offset_id and min_id:
|
||||
if offset_id - min_id <= 1:
|
||||
return
|
||||
|
||||
limit = float('inf') if limit is None else int(limit)
|
||||
if search is not None or filter or from_user:
|
||||
if filter is None:
|
||||
|
@ -1123,8 +1150,8 @@ class TelegramClient(TelegramBareClient):
|
|||
offset_id=offset_id,
|
||||
add_offset=add_offset,
|
||||
limit=1,
|
||||
max_id=max_id,
|
||||
min_id=min_id,
|
||||
max_id=0,
|
||||
min_id=0,
|
||||
hash=0,
|
||||
from_id=self.get_input_entity(from_user) if from_user else None
|
||||
)
|
||||
|
@ -1134,8 +1161,8 @@ class TelegramClient(TelegramBareClient):
|
|||
limit=1,
|
||||
offset_date=offset_date,
|
||||
offset_id=offset_id,
|
||||
min_id=min_id,
|
||||
max_id=max_id,
|
||||
min_id=0,
|
||||
max_id=0,
|
||||
add_offset=add_offset,
|
||||
hash=0
|
||||
)
|
||||
|
@ -1166,6 +1193,9 @@ class TelegramClient(TelegramBareClient):
|
|||
for x in itertools.chain(r.users, r.chats)}
|
||||
|
||||
for message in r.messages:
|
||||
if message.id <= min_id:
|
||||
return
|
||||
|
||||
if isinstance(message, MessageEmpty) or message.id >= last_id:
|
||||
continue
|
||||
|
||||
|
@ -1175,27 +1205,7 @@ class TelegramClient(TelegramBareClient):
|
|||
# IDs are returned in descending order.
|
||||
last_id = message.id
|
||||
|
||||
# Add a few extra attributes to the Message to be friendlier.
|
||||
# To make messages more friendly, always add message
|
||||
# to service messages, and action to normal messages.
|
||||
message.message = getattr(message, 'message', None)
|
||||
message.action = getattr(message, 'action', None)
|
||||
message.to = entities[utils.get_peer_id(message.to_id)]
|
||||
message.sender = (
|
||||
None if not message.from_id else
|
||||
entities[utils.get_peer_id(message.from_id)]
|
||||
)
|
||||
if getattr(message, 'fwd_from', None):
|
||||
message.fwd_from.sender = (
|
||||
None if not message.fwd_from.from_id else
|
||||
entities[utils.get_peer_id(message.fwd_from.from_id)]
|
||||
)
|
||||
message.fwd_from.channel = (
|
||||
None if not message.fwd_from.channel_id else
|
||||
entities[utils.get_peer_id(
|
||||
PeerChannel(message.fwd_from.channel_id)
|
||||
)]
|
||||
)
|
||||
self._make_message_friendly(message, entities)
|
||||
await yield_(message)
|
||||
have += 1
|
||||
|
||||
|
@ -1210,18 +1220,92 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
await asyncio.sleep(max(wait_time - (time.time() - start), 0))
|
||||
|
||||
@staticmethod
|
||||
def _make_message_friendly(message, entities):
|
||||
"""
|
||||
Add a few extra attributes to the :tl:`Message` to be friendlier.
|
||||
|
||||
To make messages more friendly, always add message
|
||||
to service messages, and action to normal messages.
|
||||
"""
|
||||
# TODO Create an actual friendlier class
|
||||
message.message = getattr(message, 'message', None)
|
||||
message.action = getattr(message, 'action', None)
|
||||
message.to = entities[utils.get_peer_id(message.to_id)]
|
||||
message.sender = (
|
||||
None if not message.from_id else
|
||||
entities[utils.get_peer_id(message.from_id)]
|
||||
)
|
||||
if getattr(message, 'fwd_from', None):
|
||||
message.fwd_from.sender = (
|
||||
None if not message.fwd_from.from_id else
|
||||
entities[utils.get_peer_id(message.fwd_from.from_id)]
|
||||
)
|
||||
message.fwd_from.channel = (
|
||||
None if not message.fwd_from.channel_id else
|
||||
entities[utils.get_peer_id(
|
||||
PeerChannel(message.fwd_from.channel_id)
|
||||
)]
|
||||
)
|
||||
|
||||
@async_generator
|
||||
async def _iter_ids(self, entity, ids, total):
|
||||
"""
|
||||
Special case for `iter_messages` when it should only fetch some IDs.
|
||||
"""
|
||||
if total:
|
||||
total[0] = len(ids)
|
||||
|
||||
if isinstance(entity, InputPeerChannel):
|
||||
r = await self(channels.GetMessagesRequest(entity, ids))
|
||||
else:
|
||||
r = await self(messages.GetMessagesRequest(ids))
|
||||
|
||||
entities = {utils.get_peer_id(x): x
|
||||
for x in itertools.chain(r.users, r.chats)}
|
||||
|
||||
# Telegram seems to return the messages in the order in which
|
||||
# we asked them for, so we don't need to check it ourselves.
|
||||
for message in r.messages:
|
||||
if isinstance(message, MessageEmpty):
|
||||
await yield_(None)
|
||||
else:
|
||||
self._make_message_friendly(message, entities)
|
||||
await yield_(message)
|
||||
|
||||
async def get_messages(self, *args, **kwargs):
|
||||
"""
|
||||
Same as :meth:`iter_messages`, but returns a list instead
|
||||
with an additional ``.total`` attribute on the list.
|
||||
|
||||
If the `limit` is not set, it will be 1 by default unless both
|
||||
`min_id` **and** `max_id` are set (as *named* arguments), in
|
||||
which case the entire range will be returned.
|
||||
|
||||
This is so because any integer limit would be rather arbitrary and
|
||||
it's common to only want to fetch one message, but if a range is
|
||||
specified it makes sense that it should return the entirety of it.
|
||||
|
||||
If `ids` is present in the *named* arguments and is not a list,
|
||||
a single :tl:`Message` will be returned for convenience instead
|
||||
of a list.
|
||||
"""
|
||||
total = [0]
|
||||
kwargs['_total'] = total
|
||||
if len(args) == 1 and 'limit' not in kwargs:
|
||||
if 'min_id' in kwargs and 'max_id' in kwargs:
|
||||
kwargs['limit'] = None
|
||||
else:
|
||||
kwargs['limit'] = 1
|
||||
|
||||
msgs = UserList()
|
||||
async for msg in self.iter_messages(*args, **kwargs):
|
||||
msgs.append(msg)
|
||||
|
||||
msgs.total = total[0]
|
||||
if 'ids' in kwargs and not utils.is_list_like(kwargs['ids']):
|
||||
return msgs[0]
|
||||
|
||||
return msgs
|
||||
|
||||
async def get_message_history(self, *args, **kwargs):
|
||||
|
@ -1666,7 +1750,7 @@ class TelegramClient(TelegramBareClient):
|
|||
if m.has('duration') else 0)
|
||||
)
|
||||
else:
|
||||
doc = DocumentAttributeVideo(0, 0, 0,
|
||||
doc = DocumentAttributeVideo(0, 1, 1,
|
||||
round_message=video_note)
|
||||
|
||||
attr_dict[DocumentAttributeVideo] = doc
|
||||
|
@ -2448,7 +2532,7 @@ class TelegramClient(TelegramBareClient):
|
|||
|
||||
async def catch_up(self):
|
||||
state = self.session.get_update_state(0)
|
||||
if not state:
|
||||
if not state or not state.pts:
|
||||
return
|
||||
|
||||
self.session.catching_up = True
|
||||
|
|
|
@ -552,8 +552,13 @@ def resolve_id(marked_id):
|
|||
if marked_id >= 0:
|
||||
return marked_id, PeerUser
|
||||
|
||||
if str(marked_id).startswith('-100'):
|
||||
return int(str(marked_id)[4:]), PeerChannel
|
||||
# There have been report of chat IDs being 10000xyz, which means their
|
||||
# marked version is -10000xyz, which in turn looks like a channel but
|
||||
# it becomes 00xyz (= xyz). Hence, we must assert that there are only
|
||||
# two zeroes.
|
||||
m = re.match(r'-100([^0]\d*)', str(marked_id))
|
||||
if m:
|
||||
return int(m.group(1)), PeerChannel
|
||||
|
||||
return -marked_id, PeerChat
|
||||
|
||||
|
|
|
@ -3,7 +3,8 @@ from getpass import getpass
|
|||
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
from telethon import ConnectionMode, TelegramClient
|
||||
from telethon import TelegramClient, events
|
||||
from telethon.network import ConnectionTcpAbridged
|
||||
from telethon.errors import SessionPasswordNeededError
|
||||
from telethon.tl.types import (
|
||||
PeerChat, UpdateShortChatMessage, UpdateShortMessage
|
||||
|
@ -70,11 +71,11 @@ class InteractiveTelegramClient(TelegramClient):
|
|||
# These parameters should be passed always, session name and API
|
||||
session_user_id, api_id, api_hash,
|
||||
|
||||
# You can optionally change the connection mode by using this enum.
|
||||
# This changes how much data will be sent over the network with
|
||||
# every request, and how it will be formatted. Default is
|
||||
# ConnectionMode.TCP_FULL, and smallest is TCP_TCP_ABRIDGED.
|
||||
connection_mode=ConnectionMode.TCP_ABRIDGED,
|
||||
# You can optionally change the connection mode by passing a
|
||||
# type or an instance of it. This changes how the sent packets
|
||||
# look (low-level concept you normally shouldn't worry about).
|
||||
# Default is ConnectionTcpFull, smallest is ConnectionTcpAbridged.
|
||||
connection=ConnectionTcpAbridged,
|
||||
|
||||
# If you're using a proxy, set it here.
|
||||
proxy=proxy,
|
||||
|
@ -126,10 +127,11 @@ class InteractiveTelegramClient(TelegramClient):
|
|||
def run(self):
|
||||
"""Main loop of the TelegramClient, will wait for user action"""
|
||||
|
||||
# Once everything is ready, we can add an update handler. Every
|
||||
# update object will be passed to the self.update_handler method,
|
||||
# where we can process it as we need.
|
||||
self.add_update_handler(self.update_handler)
|
||||
# Once everything is ready, we can add an event handler.
|
||||
#
|
||||
# Events are an abstraction over Telegram's "Updates" and
|
||||
# are much easier to use.
|
||||
self.add_event_handler(self.message_handler, events.NewMessage)
|
||||
|
||||
# Enter a while loop to chat as long as the user wants
|
||||
while True:
|
||||
|
@ -334,31 +336,29 @@ class InteractiveTelegramClient(TelegramClient):
|
|||
bytes_to_string(total_bytes), downloaded_bytes / total_bytes)
|
||||
)
|
||||
|
||||
def update_handler(self, update):
|
||||
"""Callback method for received Updates"""
|
||||
def message_handler(self, event):
|
||||
"""Callback method for received events.NewMessage"""
|
||||
|
||||
# We have full control over what we want to do with the updates.
|
||||
# In our case we only want to react to chat messages, so we use
|
||||
# isinstance() to behave accordingly on these cases.
|
||||
if isinstance(update, UpdateShortMessage):
|
||||
who = self.get_entity(update.user_id)
|
||||
if update.out:
|
||||
# Note that accessing ``.sender`` and ``.chat`` may be slow since
|
||||
# these are not cached and must be queried always! However it lets
|
||||
# us access the chat title and user name.
|
||||
if event.is_group:
|
||||
if event.out:
|
||||
sprint('>> sent "{}" to chat {}'.format(
|
||||
event.text, get_display_name(event.chat)
|
||||
))
|
||||
else:
|
||||
sprint('<< {} @ {} sent "{}"'.format(
|
||||
get_display_name(event.sender),
|
||||
get_display_name(event.chat),
|
||||
event.text
|
||||
))
|
||||
else:
|
||||
if event.out:
|
||||
sprint('>> "{}" to user {}'.format(
|
||||
update.message, get_display_name(who)
|
||||
event.text, get_display_name(event.chat)
|
||||
))
|
||||
else:
|
||||
sprint('<< {} sent "{}"'.format(
|
||||
get_display_name(who), update.message
|
||||
))
|
||||
|
||||
elif isinstance(update, UpdateShortChatMessage):
|
||||
which = self.get_entity(PeerChat(update.chat_id))
|
||||
if update.out:
|
||||
sprint('>> sent "{}" to chat {}'.format(
|
||||
update.message, get_display_name(which)
|
||||
))
|
||||
else:
|
||||
who = self.get_entity(update.from_id)
|
||||
sprint('<< {} @ {} sent "{}"'.format(
|
||||
get_display_name(which), get_display_name(who), update.message
|
||||
get_display_name(event.chat), event.text
|
||||
))
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
#!/usr/bin/env python3
|
||||
# A simple script to print all updates received
|
||||
#
|
||||
# NOTE: To run this script you MUST have 'TG_API_ID' and 'TG_API_HASH' in
|
||||
# your environment variables. This is a good way to use these private
|
||||
# values. See https://superuser.com/q/284342.
|
||||
|
||||
from os import environ
|
||||
|
||||
|
@ -23,7 +27,7 @@ def main():
|
|||
else:
|
||||
client.start()
|
||||
|
||||
client.add_update_handler(update_handler)
|
||||
client.add_event_handler(update_handler)
|
||||
print('(Press Ctrl+C to stop this)')
|
||||
client.idle()
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@
|
|||
"""
|
||||
A example script to automatically send messages based on certain triggers.
|
||||
|
||||
The script makes uses of environment variables to determine the API ID,
|
||||
hash, phone and such to be used. You may want to add these to your .bashrc
|
||||
file, including TG_API_ID, TG_API_HASH, TG_PHONE and optionally TG_SESSION.
|
||||
NOTE: To run this script you MUST have 'TG_API_ID' and 'TG_API_HASH' in
|
||||
your environment variables. This is a good way to use these private
|
||||
values. See https://superuser.com/q/284342.
|
||||
|
||||
This script assumes that you have certain files on the working directory,
|
||||
such as "xfiles.m4a" or "anytime.png" for some of the automated replies.
|
||||
|
|
|
@ -4,7 +4,36 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Telethon API</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="css/docs.css" rel="stylesheet">
|
||||
<link id="style" href="css/docs.light.css" rel="stylesheet">
|
||||
<script>
|
||||
(function () {
|
||||
var style = document.getElementById('style');
|
||||
|
||||
// setTheme(<link />, 'light' / 'dark')
|
||||
function setTheme(theme) {
|
||||
document.cookie = 'css=' + theme + '; path=/';
|
||||
return style.href = 'css/docs.' + theme + '.css';
|
||||
}
|
||||
|
||||
// setThemeOnClick(<link />, 'light' / 'dark', <a />)
|
||||
function setThemeOnClick(theme, button) {
|
||||
return button.addEventListener('click', function (e) {
|
||||
setTheme(theme);
|
||||
e.preventDefault();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
setTheme(document.cookie
|
||||
.split(';')[0]
|
||||
.split('=')[1] || 'light');
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
setThemeOnClick('light', document.getElementById('themeLight'));
|
||||
setThemeOnClick('dark', document.getElementById('themeDark'));
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
|
@ -21,7 +50,10 @@
|
|||
on what the methods, constructors and types mean. Nevertheless, this
|
||||
page aims to provide easy access to all the available methods, their
|
||||
definition and parameters.</p>
|
||||
|
||||
<p id="themeSelect">
|
||||
<a href="#" id="themeLight">light</a> /
|
||||
<a href="#" id="themeDark">dark</a> theme.
|
||||
</p>
|
||||
<p>Please note that when you see this:</p>
|
||||
<pre>---functions---
|
||||
users.getUsers#0d91a548 id:Vector<InputUser> = Vector<User></pre>
|
||||
|
|
185
telethon_generator/data/html/css/docs.dark.css
Normal file
185
telethon_generator/data/html/css/docs.dark.css
Normal file
|
@ -0,0 +1,185 @@
|
|||
body {
|
||||
font-family: 'Nunito', sans-serif;
|
||||
color: #bbb;
|
||||
background-color:#000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #42aaed;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
padding: 8px;
|
||||
color: #567;
|
||||
background: #080a0c;
|
||||
border-radius: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #64bbdd;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-top: 1px solid #111;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
margin-bottom: 16px;
|
||||
list-style: none;
|
||||
background: #080a0c;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.horizontal li {
|
||||
display: inline-block;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.horizontal img {
|
||||
display: inline-block;
|
||||
margin: 0 8px -2px 0;
|
||||
}
|
||||
|
||||
h1, summary.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#main_div {
|
||||
padding: 20px 0;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-track:horizontal {
|
||||
background: #222;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb:horizontal {
|
||||
background: #444;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
:target {
|
||||
border: 2px solid #149;
|
||||
background: #246;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* 'sh' stands for Syntax Highlight */
|
||||
span.sh1 {
|
||||
color: #f93;
|
||||
}
|
||||
|
||||
span.tooltip {
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 20px;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 2px;
|
||||
border: 2px solid #222;
|
||||
background: #000;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
#searchBox:placeholder-shown {
|
||||
color: #bbb;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 2px;
|
||||
font-size: 16px;
|
||||
padding: 8px;
|
||||
color: #bbb;
|
||||
background-color: #111;
|
||||
border: 2px solid #146;
|
||||
transition-duration: 300ms;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #146;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* https://www.w3schools.com/css/css_navbar.asp */
|
||||
ul.together {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul.together li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul.together li a {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
background: #111;
|
||||
padding: 4px 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/30810322 */
|
||||
.invisible {
|
||||
left: 0;
|
||||
top: -99px;
|
||||
padding: 0;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: fixed;
|
||||
box-shadow: none;
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1, summary.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#dev_page_content_wrap {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
#dev_page_title {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
229
telethon_generator/data/html/css/docs.h4x0r.css
Normal file
229
telethon_generator/data/html/css/docs.h4x0r.css
Normal file
|
@ -0,0 +1,229 @@
|
|||
/* Begin of https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css
|
||||
*
|
||||
* Hack typeface https://github.com/source-foundry/Hack
|
||||
* License: https://github.com/source-foundry/Hack/blob/master/LICENSE.md
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
src: url('fonts/hack-regular.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-regular.woff?sha=3114f1256') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
src: url('fonts/hack-bold.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bold.woff?sha=3114f1256') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
src: url('fonts/hack-italic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-italic.woff?sha=3114f1256') format('woff');
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Hack';
|
||||
src: url('fonts/hack-bolditalic.woff2?sha=3114f1256') format('woff2'), url('fonts/hack-bolditalic.woff?sha=3114f1256') format('woff');
|
||||
font-weight: 700;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* End of https://cdn.jsdelivr.net/npm/hack-font@3/build/web/hack.css */
|
||||
|
||||
body {
|
||||
font-family: 'Hack', monospace;
|
||||
color: #0a0;
|
||||
background-color: #000;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
color: #000;
|
||||
background: #0a0;
|
||||
}
|
||||
|
||||
::selection {
|
||||
color: #000;
|
||||
background: #0a0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0a0;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 8px;
|
||||
color: #0c0;
|
||||
background: #010;
|
||||
border-radius: 0;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0f0;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
table td {
|
||||
border-top: 1px solid #111;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.horizontal {
|
||||
margin-bottom: 16px;
|
||||
list-style: none;
|
||||
background: #010;
|
||||
border-radius: 4px;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
.horizontal li {
|
||||
display: inline-block;
|
||||
margin: 0 8px 0 0;
|
||||
}
|
||||
|
||||
.horizontal img {
|
||||
opacity: 0;
|
||||
display: inline-block;
|
||||
margin: 0 8px -2px 0;
|
||||
}
|
||||
|
||||
h1, summary.title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
#main_div {
|
||||
padding: 20px 0;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar {
|
||||
visibility: visible;
|
||||
display: block;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-track:horizontal {
|
||||
background: #222;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
pre::-webkit-scrollbar-thumb:horizontal {
|
||||
background: #444;
|
||||
border-radius: 0;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
:target {
|
||||
border: 2px solid #0f0;
|
||||
background: #010;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
/* 'sh' stands for Syntax Highlight */
|
||||
span.sh1 {
|
||||
color: #0f0;
|
||||
}
|
||||
|
||||
span.tooltip {
|
||||
border-bottom: 1px dashed #ddd;
|
||||
}
|
||||
|
||||
#searchBox {
|
||||
width: 100%;
|
||||
border: none;
|
||||
height: 20px;
|
||||
padding: 8px;
|
||||
font-size: 16px;
|
||||
border-radius: 2px;
|
||||
border: 2px solid #222;
|
||||
background: #000;
|
||||
color: #0e0;
|
||||
font-family: 'Hack', monospace;
|
||||
}
|
||||
|
||||
#searchBox:placeholder-shown {
|
||||
color: #0b0;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
button {
|
||||
font-size: 16px;
|
||||
padding: 8px;
|
||||
color: #0f0;
|
||||
background-color: #071007;
|
||||
border: 2px solid #131;
|
||||
transition-duration: 300ms;
|
||||
font-family: 'Hack', monospace;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background-color: #131;
|
||||
}
|
||||
|
||||
/* https://www.w3schools.com/css/css_navbar.asp */
|
||||
ul.together {
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
ul.together li {
|
||||
float: left;
|
||||
}
|
||||
|
||||
ul.together li a {
|
||||
display: block;
|
||||
border-radius: 8px;
|
||||
background: #121;
|
||||
padding: 4px 8px;
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
/* https://stackoverflow.com/a/30810322 */
|
||||
.invisible {
|
||||
left: 0;
|
||||
top: -99px;
|
||||
padding: 0;
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
border: none;
|
||||
outline: none;
|
||||
position: fixed;
|
||||
box-shadow: none;
|
||||
color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
h1, summary.title {
|
||||
font-size: 18px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
#dev_page_content_wrap {
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
#dev_page_title {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
|
@ -95,19 +95,6 @@ span.sh1 {
|
|||
color: #f70;
|
||||
}
|
||||
|
||||
span.sh2 {
|
||||
color: #0c7;
|
||||
}
|
||||
|
||||
span.sh3 {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
span.sh4 {
|
||||
color: #06c;
|
||||
}
|
||||
|
||||
span.tooltip {
|
||||
border-bottom: 1px dashed #444;
|
||||
}
|
|
@ -31,29 +31,32 @@ class DocsWriter:
|
|||
self._script = ''
|
||||
|
||||
# High level writing
|
||||
def write_head(self, title, relative_css_path):
|
||||
def write_head(self, title, relative_css_path, default_css):
|
||||
"""Writes the head part for the generated document,
|
||||
with the given title and CSS
|
||||
"""
|
||||
self.write('''<!DOCTYPE html>
|
||||
self.write(
|
||||
'''<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>''')
|
||||
|
||||
self.write(title)
|
||||
|
||||
self.write('''</title>
|
||||
<title>{title}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link href="''')
|
||||
|
||||
self.write(relative_css_path)
|
||||
|
||||
self.write('''" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro" rel="stylesheet">
|
||||
<link id="style" href="{rel_css}/docs.{def_css}.css" rel="stylesheet">
|
||||
<script>
|
||||
document.getElementById("style").href = "{rel_css}/docs."
|
||||
+ (document.cookie.split(";")[0].split("=")[1] || "{def_css}")
|
||||
+ ".css";
|
||||
</script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="main_div">''')
|
||||
<div id="main_div">''',
|
||||
title=title,
|
||||
rel_css=relative_css_path.rstrip('/'),
|
||||
def_css=default_css
|
||||
)
|
||||
|
||||
def set_menu_separator(self, relative_image_path):
|
||||
"""Sets the menu separator.
|
||||
|
@ -77,9 +80,7 @@ class DocsWriter:
|
|||
|
||||
self.write('<li>')
|
||||
if link:
|
||||
self.write('<a href="')
|
||||
self.write(link)
|
||||
self.write('">')
|
||||
self.write('<a href="{}">', link)
|
||||
|
||||
# Write the real menu entry text
|
||||
self.write(name)
|
||||
|
@ -98,26 +99,21 @@ class DocsWriter:
|
|||
"""Writes a title header in the document body,
|
||||
with an optional depth level
|
||||
"""
|
||||
self.write('<h%d>' % level)
|
||||
self.write(title)
|
||||
self.write('</h%d>' % level)
|
||||
self.write('<h{level}>{title}</h{level}>', title=title, level=level)
|
||||
|
||||
def write_code(self, tlobject):
|
||||
"""Writes the code for the given 'tlobject' properly
|
||||
formatted with hyperlinks
|
||||
"""
|
||||
self.write('<pre>---')
|
||||
self.write('functions' if tlobject.is_function else 'types')
|
||||
self.write('---\n')
|
||||
self.write('<pre>---{}---\n',
|
||||
'functions' if tlobject.is_function else 'types')
|
||||
|
||||
# Write the function or type and its ID
|
||||
if tlobject.namespace:
|
||||
self.write(tlobject.namespace)
|
||||
self.write('.')
|
||||
|
||||
self.write(tlobject.name)
|
||||
self.write('#')
|
||||
self.write(hex(tlobject.id)[2:].rjust(8, '0'))
|
||||
self.write('{}#{:08x}', tlobject.name, tlobject.id)
|
||||
|
||||
# Write all the arguments (or do nothing if there's none)
|
||||
for arg in tlobject.args:
|
||||
|
@ -134,20 +130,19 @@ class DocsWriter:
|
|||
|
||||
# "Opening" modifiers
|
||||
if arg.is_flag:
|
||||
self.write('flags.%d?' % arg.flag_index)
|
||||
self.write('flags.{}?', arg.flag_index)
|
||||
|
||||
if arg.is_generic:
|
||||
self.write('!')
|
||||
|
||||
if arg.is_vector:
|
||||
self.write(
|
||||
'<a href="%s">Vector</a><' % self.type_to_path('vector')
|
||||
)
|
||||
self.write('<a href="{}">Vector</a><',
|
||||
self.type_to_path('vector'))
|
||||
|
||||
# Argument type
|
||||
if arg.type:
|
||||
if add_link:
|
||||
self.write('<a href="%s">' % self.type_to_path(arg.type))
|
||||
self.write('<a href="{}">', self.type_to_path(arg.type))
|
||||
self.write(arg.type)
|
||||
if add_link:
|
||||
self.write('</a>')
|
||||
|
@ -176,19 +171,14 @@ class DocsWriter:
|
|||
# use a lower type name for it (see #81)
|
||||
vector, inner = tlobject.result.split('<')
|
||||
inner = inner.strip('>')
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(vector))
|
||||
self.write('">%s</a><' % vector)
|
||||
self.write('<a href="{}">{}</a><',
|
||||
self.type_to_path(vector), vector)
|
||||
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(inner))
|
||||
self.write('">%s</a>' % inner)
|
||||
|
||||
self.write('>')
|
||||
self.write('<a href="{}">{}</a>>',
|
||||
self.type_to_path(inner), inner)
|
||||
else:
|
||||
self.write('<a href="')
|
||||
self.write(self.type_to_path(tlobject.result))
|
||||
self.write('">%s</a>' % tlobject.result)
|
||||
self.write('<a href="{}">{}</a>',
|
||||
self.type_to_path(tlobject.result), tlobject.result)
|
||||
|
||||
self.write('</pre>')
|
||||
|
||||
|
@ -209,17 +199,13 @@ class DocsWriter:
|
|||
|
||||
self.write('<td')
|
||||
if align:
|
||||
self.write(' style="text-align:')
|
||||
self.write(align)
|
||||
self.write('"')
|
||||
self.write(' style="text-align:{}"', align)
|
||||
self.write('>')
|
||||
|
||||
if bold:
|
||||
self.write('<b>')
|
||||
if link:
|
||||
self.write('<a href="')
|
||||
self.write(link)
|
||||
self.write('">')
|
||||
self.write('<a href="{}">', link)
|
||||
|
||||
# Finally write the real table data, the given text
|
||||
self.write(text)
|
||||
|
@ -244,9 +230,7 @@ class DocsWriter:
|
|||
|
||||
def write_text(self, text):
|
||||
"""Writes a paragraph of text"""
|
||||
self.write('<p>')
|
||||
self.write(text)
|
||||
self.write('</p>')
|
||||
self.write('<p>{}</p>', text)
|
||||
|
||||
def write_copy_button(self, text, text_to_copy):
|
||||
"""Writes a button with 'text' which can be used
|
||||
|
@ -273,16 +257,18 @@ class DocsWriter:
|
|||
'c.select();'
|
||||
'try{document.execCommand("copy")}'
|
||||
'catch(e){}}'
|
||||
'</script>')
|
||||
'</script>'
|
||||
)
|
||||
|
||||
self.write('</div>')
|
||||
self.write(self._script)
|
||||
self.write('</body></html>')
|
||||
self.write('</div>{}</body></html>', self._script)
|
||||
|
||||
# "Low" level writing
|
||||
def write(self, s):
|
||||
def write(self, s, *args, **kwargs):
|
||||
"""Wrapper around handle.write"""
|
||||
self.handle.write(s)
|
||||
if args or kwargs:
|
||||
self.handle.write(s.format(*args, **kwargs))
|
||||
else:
|
||||
self.handle.write(s)
|
||||
|
||||
# With block
|
||||
def __enter__(self):
|
||||
|
|
|
@ -33,14 +33,15 @@ def get_import_code(tlobject):
|
|||
.format(kind, ns, tlobject.class_name)
|
||||
|
||||
|
||||
def _get_create_path_for(root, tlobject):
|
||||
def _get_create_path_for(root, tlobject, make=True):
|
||||
"""Creates and returns the path for the given TLObject at root."""
|
||||
out_dir = 'methods' if tlobject.is_function else 'constructors'
|
||||
if tlobject.namespace:
|
||||
out_dir = os.path.join(out_dir, tlobject.namespace)
|
||||
|
||||
out_dir = os.path.join(root, out_dir)
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
if make:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
return os.path.join(out_dir, _get_file_name(tlobject))
|
||||
|
||||
|
||||
|
@ -114,7 +115,9 @@ def _generate_index(folder, original_paths, root):
|
|||
filename = os.path.join(folder, 'index.html')
|
||||
with DocsWriter(filename, type_to_path=_get_path_for_type) as docs:
|
||||
# Title should be the current folder name
|
||||
docs.write_head(folder.title(), relative_css_path=paths['css'])
|
||||
docs.write_head(folder.title(),
|
||||
relative_css_path=paths['css'],
|
||||
default_css=original_paths['default_css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
_build_menu(docs, filename, root,
|
||||
|
@ -206,7 +209,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
# * Generating the types documentation, showing available constructors.
|
||||
# TODO Tried using 'defaultdict(list)' with strange results, make it work.
|
||||
original_paths = {
|
||||
'css': 'css/docs.css',
|
||||
'css': 'css',
|
||||
'arrow': 'img/arrow.svg',
|
||||
'search.js': 'js/search.js',
|
||||
'404': '404.html',
|
||||
|
@ -218,6 +221,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
original_paths = {k: os.path.join(output_dir, v)
|
||||
for k, v in original_paths.items()}
|
||||
|
||||
original_paths['default_css'] = 'light' # docs.<name>.css, local path
|
||||
type_to_constructors = {}
|
||||
type_to_functions = {}
|
||||
for tlobject in tlobjects:
|
||||
|
@ -251,7 +255,8 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
|
||||
with DocsWriter(filename, type_to_path=path_for_type) as docs:
|
||||
docs.write_head(title=tlobject.class_name,
|
||||
relative_css_path=paths['css'])
|
||||
relative_css_path=paths['css'],
|
||||
default_css=original_paths['default_css'])
|
||||
|
||||
# Create the menu (path to the current TLObject)
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
|
@ -392,9 +397,9 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
for k, v in original_paths.items()}
|
||||
|
||||
with DocsWriter(filename, type_to_path=path_for_type) as docs:
|
||||
docs.write_head(
|
||||
title=snake_to_camel_case(name),
|
||||
relative_css_path=paths['css'])
|
||||
docs.write_head(title=snake_to_camel_case(name),
|
||||
relative_css_path=paths['css'],
|
||||
default_css=original_paths['default_css'])
|
||||
|
||||
docs.set_menu_separator(paths['arrow'])
|
||||
_build_menu(docs, filename, output_dir,
|
||||
|
@ -549,7 +554,7 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
type_names = fmt(types, formatter=lambda x: x)
|
||||
|
||||
# Local URLs shouldn't rely on the output's root, so set empty root
|
||||
create_path_for = functools.partial(_get_create_path_for, '')
|
||||
create_path_for = functools.partial(_get_create_path_for, '', make=False)
|
||||
path_for_type = functools.partial(_get_path_for_type, '')
|
||||
request_urls = fmt(methods, create_path_for)
|
||||
type_urls = fmt(types, path_for_type)
|
||||
|
@ -570,7 +575,8 @@ def _write_html_pages(tlobjects, errors, layer, input_res, output_dir):
|
|||
|
||||
|
||||
def _copy_resources(res_dir, out_dir):
|
||||
for dirname, files in [('css', ['docs.css']), ('img', ['arrow.svg'])]:
|
||||
for dirname, files in [('css', ['docs.light.css', 'docs.dark.css']),
|
||||
('img', ['arrow.svg'])]:
|
||||
dirpath = os.path.join(out_dir, dirname)
|
||||
os.makedirs(dirpath, exist_ok=True)
|
||||
for file in files:
|
||||
|
|
|
@ -477,6 +477,12 @@ def _write_arg_to_bytes(builder, arg, args, name=None):
|
|||
# Else it may be a custom type
|
||||
builder.write('bytes({})', name)
|
||||
|
||||
# If the type is not boxed (i.e. starts with lowercase) we should
|
||||
# not serialize the constructor ID (so remove its first 4 bytes).
|
||||
boxed = arg.type[arg.type.find('.') + 1].isupper()
|
||||
if not boxed:
|
||||
builder.write('[4:]')
|
||||
|
||||
if arg.is_flag:
|
||||
builder.write(')')
|
||||
if arg.is_vector:
|
||||
|
|
Loading…
Reference in New Issue
Block a user