Packages
MTProto (Telegram) implementation for Elixir.
Retired package: Deprecated - This package is not maintained anymore.
Current section
Files
Jump to
Current section
Files
lib/mtproto/session.ex
defmodule MTProto.Session do
require Logger
alias MTProto.{Crypto, Registry, Session}
alias MTProto.Session.{HandlerSupervisor, ListenerSupervisor}
@table SessionRegistry
@moduledoc """
Provide advanced control over sessions.
## Session
```
%MTProto.Session{auth_key: <<0, 0, 0, 0, 0, 0, 0, 0>>, b: nil, client: nil,
dc: nil, dh_prime: nil, g_a: nil, handler: nil, initialized?: false,
last_msg_id: 0, listener: nil, msg_seqno: 0, new_nonce: nil,
phone_code_hash: nil, seqno: 0, server_nonce: nil, server_salt: 0, socket: 0,
user_id: nil}
```
* `:user_id` - Telegram ID of the user
* `:auth_key` - authorization key, default to `<<0::8*8>>`
* `server_salt` - default to `0`
* `:handler` - PID of the process handling messages (parse, dispatch, send);
* `:listener` - PID of the process listening for incoming messages
* `:dc` - id of the datacenter used by the session (1,2,3,4,5). See
`MTProto.DC`
* `:initialized` - was the session initialized ? (See
`MTProto.API.init_connection/5`)
* `:phone_code_hash` - `phone_code_hash` returned when sending code (SMS, call,
open client)
* `:seqno` - sequence number
* `:msg_seqno` - message sequence number
* `:socket` - socket used to receive and send message (to Telegram's servers)
"""
defstruct auth_key: <<0::8*8>>,
server_salt: 0,
user_id: nil,
handler: nil,
listener: nil,
dc: nil,
initialized?: false,
client: nil,
phone_code_hash: nil,
last_msg_id: 0,
seqno: 0,
msg_seqno: 0,
socket: 0,
new_nonce: nil, # auth key computation
server_nonce: nil, # auth key computation
g_a: nil, # auth key computation
b: nil, # auth key computation
dh_prime: nil #auth key computation
####
# Registry access
def get(id), do: Registry.get @table, id
def get_all(), do: Registry.dump @table
def set(id, value), do: Registry.set @table, id, value
def update(id, value) do
session = Session.get id
Session.set id, struct(session, value)
end
def drop(id), do: Registry.drop @table, id
###
@doc """
Open a new session. `dc_id` is used to select which datacenter to connect,
`client` is the PID of the process to be notified when receiving new messages.
Returns the `session_id`.
"""
def open(dc_id, client \\ nil) do
session_id = Crypto.rand_bytes(8)
Session.set session_id, struct(Session, dc: dc_id, client: client)
{:ok, _} = HandlerSupervisor.pop(session_id, dc_id)
{:ok, _} = ListenerSupervisor.pop(session_id)
session_id
end
@doc """
Close the current connection to Telegram's server and open a new one to
the given DC.
* `dc_id` - ID of the DC to connect.
"""
def reconnect(session_id, dc_id) do
# Update session's DC
Session.update(session_id, dc: dc_id)
# Close old socket and open a new one
:ok = ListenerSupervisor.drop(session_id)
{:ok, _} = ListenerSupervisor.pop(session_id)
# Generate a new authorization key if necessary
if Session.get(session_id).auth_key == <<0::8*8>> do
Logger.debug "No authorization key found for session #{session_id}.
Requesting..."
MTProto.Auth.generate(session_id)
end
end
@doc """
Close a session given its id. Stop both listener and handler, remove the
session from the registry.
"""
def close(session_id) do
:ok = ListenerSupervisor.drop(session_id)
:ok = HandlerSupervisor.drop(session_id)
Session.drop(session_id)
end
@doc """
Send a message to telegram's servers on session `session_id`. Default
behavior is to send an encrypted message : you can send a plaintext message
by specifying :plain as third argument. Returns :
* `{:ok, message_id}`
* `{:err, reason}`
"""
def send(session_id, message, type \\ :encrypted) do
session = Session.get(session_id)
call = if type == :plain, do: :send_plain, else: :send
GenServer.call session.handler, {call, message}
end
@doc """
Set the PID (`client`) of the process to be notified when receiving new
messages on session `session_id`.
"""
def set_client(session_id, client) do
Session.update(session_id, client: client)
end
@doc """
Export `{user_id, auth_key, server_salt}` from a session.
"""
def export(session_id) do
session = Session.get(session_id)
{session.user_id, session.auth_key, session.server_salt}
end
@doc """
Import `user_id`, `auth_key` and `server_salt` to a session.
"""
def import(session_id, user_id, auth_key, server_salt) do
Session.update session_id, %{
user_id: user_id, auth_key: auth_key, server_salt: server_salt
}
end
end