Create a StringSession

This commit is contained in:
Lonami Exo 2018-08-05 16:24:34 +02:00
parent 63174ae404
commit 0011f19f8b
3 changed files with 123 additions and 67 deletions

View File

@ -36,8 +36,9 @@ If you're not going to work with updates, or don't need to cache the
``access_hash`` associated with the entities' ID, you can disable this ``access_hash`` associated with the entities' ID, you can disable this
by setting ``client.session.save_entities = False``. by setting ``client.session.save_entities = False``.
Custom Session Storage
---------------------- Different Session Storage
*************************
If you don't want to use the default SQLite session storage, you can also use If you don't want to use the default SQLite session storage, you can also use
one of the other implementations or implement your own storage. one of the other implementations or implement your own storage.
@ -46,89 +47,100 @@ To use a custom session storage, simply pass the custom session instance to
:ref:`TelegramClient <telethon-client>` instead of :ref:`TelegramClient <telethon-client>` instead of
the session name. the session name.
Telethon contains two implementations of the abstract ``Session`` class: Telethon contains three implementations of the abstract ``Session`` class:
* ``MemorySession``: stores session data in Python variables. * ``MemorySession``: stores session data within memory.
* ``SQLiteSession``, (default): stores sessions in their own SQLite databases. * ``SQLiteSession``: stores sessions within on-disk SQLite databases. Default.
* ``StringSession``: stores session data within memory,
but can be saved as a string.
You can import these ``from telethon.sessions``. For example, using the
``StringSession`` is done as follows:
.. code-block:: python
from telethon.sync import TelegramClient
from telethon.sessions import StringSession
with TelegramClient(StringSession(string), api_id, api_hash) as client:
... # use the client
# Save the string session as a string; you should decide how
# you want to save this information (over a socket, remote
# database, print it and then paste the string in the code,
# etc.); the advantage is that you don't need to save it
# on the current disk as a separate file, and can be reused
# anywhere else once you log in.
string = client.session.save()
# Note that it's also possible to save any other session type
# as a string by using ``StringSession.save(session_instance)``:
client = TelegramClient('sqlite-session', api_id, api_hash)
string = StringSession.save(client.session)
There are other community-maintained implementations available: There are other community-maintained implementations available:
* `SQLAlchemy <https://github.com/tulir/telethon-session-sqlalchemy>`_: stores all sessions in a single database via SQLAlchemy. * `SQLAlchemy <https://github.com/tulir/telethon-session-sqlalchemy>`_:
* `Redis <https://github.com/ezdev128/telethon-session-redis>`_: stores all sessions in a single Redis data store. stores all sessions in a single database via SQLAlchemy.
Creating your own storage * `Redis <https://github.com/ezdev128/telethon-session-redis>`_:
~~~~~~~~~~~~~~~~~~~~~~~~~ stores all sessions in a single Redis data store.
The easiest way to create your own storage implementation is to use ``MemorySession`` Creating your Own Storage
as the base and check out how ``SQLiteSession`` or one of the community-maintained *************************
implementations work. You can find the relevant Python files under the ``sessions``
directory in Telethon.
After you have made your own implementation, you can add it to the community-maintained The easiest way to create your own storage implementation is to use
session implementation list above with a pull request. ``MemorySession`` as the base and check out how ``SQLiteSession`` or
one of the community-maintained implementations work. You can find the
relevant Python files under the ``sessions`` directory in Telethon.
SQLite Sessions and Heroku After you have made your own implementation, you can add it to the
-------------------------- community-maintained session implementation list above with a pull request.
You probably have a newer version of SQLite installed (>= 3.8.2). Heroku uses
SQLite 3.7.9 which does not support ``WITHOUT ROWID``. So, if you generated
your session file on a system with SQLite >= 3.8.2 your session file will not
work on Heroku's platform and will throw a corrupted schema error.
There are multiple ways to solve this, the easiest of which is generating a
session file on your Heroku dyno itself. The most complicated is creating
a custom buildpack to install SQLite >= 3.8.2.
Generating a SQLite Session File on a Heroku Dyno String Sessions
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ***************
.. note:: ``StringSession`` are a convenient way to embed your login credentials
Due to Heroku's ephemeral filesystem all dynamically generated directly into your code for extremely easy portability, since all they
files not part of your applications buildpack or codebase are destroyed take is a string to be able to login without asking for your phone and
upon each restart. code (or faster start if you're using a bot token).
The easiest way to generate a string session is as follows:
.. code-block:: python
from telethon.sync import TelegramClient
from telethon.sessions import StringSession
with TelegramClient(StringSession(), api_id, api_hash) as client:
print(client.session.save())
Think of this as a way to export your authorization key (what's needed
to login into your account). This will print a string in the standard
output (likely your terminal).
.. warning:: .. warning::
Do not restart your application Dyno at any point prior to retrieving your
session file. Constantly creating new session files from Telegram's API
will result in a 24 hour rate limit ban.
Due to Heroku's ephemeral filesystem all dynamically generated **Keep this string safe!** Anyone with this string can use it
files not part of your applications buildpack or codebase are destroyed upon to login into your account and do anything they want to to do.
each restart.
Using this scaffolded code we can start the authentication process: This is similar to leaking your ``*.session`` files online,
but it is easier to leak a string than it is to leak a file.
.. code-block:: python
client = TelegramClient('login.session', api_id, api_hash).start() Once you have the string (which is a bit long), load it into your script
somehow. You can use a normal text file and ``open(...).read()`` it or
you can save it in a variable directly:
At this point your Dyno will crash because you cannot access stdin. Open your .. code-block:: python
Dyno's control panel on the Heroku website and "Run console" from the "More"
dropdown at the top right. Enter ``bash`` and wait for it to load.
You will automatically be placed into your applications working directory. string = '1aaNk8EX-YRfwoRsebUkugFvht6DUPi_Q25UOCzOAqzc...'
So run your application ``python app.py`` and now you can complete the input with TelegramClient(StringSession(string), api_id, api_hash) as client:
requests such as "what is your phone number" etc. client.send_message('me', 'Hi')
Once you're successfully authenticated exit your application script with
CTRL + C and ``ls`` to confirm ``login.session`` exists in your current
directory. Now you can create a git repo on your account and commit
``login.session`` to that repo.
You cannot ``ssh`` into your Dyno instance because it has crashed, so unless These strings are really convenient for using in places like Heroku since
you programatically upload this file to a server host this is the only way to their ephemeral filesystem will delete external files once your application
get it off of your Dyno. is over.
You now have a session file compatible with SQLite <= 3.8.2. Now you can
programatically fetch this file from an external host (Firebase, S3 etc.)
and login to your session using the following scaffolded code:
.. code-block:: python
fileName, headers = urllib.request.urlretrieve(file_url, 'login.session')
client = TelegramClient(os.path.abspath(fileName), api_id, api_hash).start()
.. note::
- ``urlretrieve`` will be depreciated, consider using ``requests``.
- ``file_url`` represents the location of your file.

View File

@ -1,3 +1,4 @@
from .abstract import Session from .abstract import Session
from .memory import MemorySession from .memory import MemorySession
from .sqlite import SQLiteSession from .sqlite import SQLiteSession
from .string import StringSession

View File

@ -0,0 +1,43 @@
import base64
import ipaddress
import struct
from .memory import MemorySession
from ..crypto import AuthKey
CURRENT_VERSION = '1'
class StringSession(MemorySession):
"""
This minimal session file can be easily saved and loaded as a string.
It is thought to be used where you don't want to create any on-disk
files but would still like to be able to save and load existing sessions
by other means.
"""
def __init__(self, string=None):
super().__init__()
if string and string[0] != CURRENT_VERSION:
raise ValueError('Not a valid string')
ip_len = 4 if len(string) == 353 else 16
self._dc_id, ip, self._port, key = struct.unpack(
'>B{}sH256s'.format(ip_len), base64.urlsafe_b64decode(string[1:]))
self._server_address = ipaddress.ip_address(ip).compressed
if any(key):
self._auth_key = AuthKey(key)
def save(self):
if not self._auth_key:
return ''
ip = ipaddress.ip_address(self._server_address).packed
return CURRENT_VERSION + base64.urlsafe_b64encode(struct.pack(
'>B{}sH256s'.format(len(ip)),
self._dc_id,
ip,
self._port,
self._auth_key.key
)).decode('ascii')