Release v0.2

[FIX] fixed code/password forever waiting
[UPD] now completely removing session (from fs too) when /logout
[UPD] most of log messages moved to debug
[UPD] updated help message
This commit is contained in:
annelin 2019-04-09 09:42:42 +03:00
parent 599f91767e
commit 714d9b4f41
3 changed files with 59 additions and 36 deletions

View file

@ -32,7 +32,7 @@ class TelegramClient
@files_dir = File.dirname(__FILE__) + '/../sessions/' + @xmpp.user_jid + '/files/' @files_dir = File.dirname(__FILE__) + '/../sessions/' + @xmpp.user_jid + '/files/'
# spawn telegram client and specify callback handlers # spawn telegram client and specify callback handlers
@logger.info 'Spawning Telegram client instance..' @logger.info 'Connecting to Telegram network..'
@client = TD::Client.new(database_directory: 'sessions/' + @xmpp.user_jid, files_directory: 'sessions/' + @xmpp.user_jid + '/files/') # create telegram client instance @client = TD::Client.new(database_directory: 'sessions/' + @xmpp.user_jid, files_directory: 'sessions/' + @xmpp.user_jid + '/files/') # create telegram client instance
@client.on(TD::Types::Update::AuthorizationState) do |update| self.auth_handler(update) end # register auth update handler @client.on(TD::Types::Update::AuthorizationState) do |update| self.auth_handler(update) end # register auth update handler
@client.on(TD::Types::Update::NewMessage) do |update| self.message_handler(update) end # register new message update handler @client.on(TD::Types::Update::NewMessage) do |update| self.message_handler(update) end # register new message update handler
@ -49,7 +49,7 @@ class TelegramClient
self.process_outgoing_msg(@xmpp.message_queue.pop) unless @xmpp.message_queue.empty? # found something in message queue self.process_outgoing_msg(@xmpp.message_queue.pop) unless @xmpp.message_queue.empty? # found something in message queue
self.process_auth(:code, @xmpp.tg_auth_data[:code]) unless @xmpp.tg_auth_data[:code].nil? # found code in auth queue self.process_auth(:code, @xmpp.tg_auth_data[:code]) unless @xmpp.tg_auth_data[:code].nil? # found code in auth queue
self.process_auth(:password, @xmpp.tg_auth_data[:password]) unless @xmpp.tg_auth_data[:password].nil? # found 2fa password in auth queue self.process_auth(:password, @xmpp.tg_auth_data[:password]) unless @xmpp.tg_auth_data[:password].nil? # found 2fa password in auth queue
sleep 0.5 sleep 0.1
end end
rescue Exception => e rescue Exception => e
@logger.error 'Unexcepted exception! %s' % e.to_s @logger.error 'Unexcepted exception! %s' % e.to_s
@ -83,7 +83,6 @@ class TelegramClient
# authorization successful -- indicate that client is online and retrieve contact list # # authorization successful -- indicate that client is online and retrieve contact list #
when TD::Types::AuthorizationState::Ready when TD::Types::AuthorizationState::Ready
@logger.info 'Authorization successful!' @logger.info 'Authorization successful!'
@xmpp.send_message(nil, 'Authorization successful.')
@xmpp.online! @xmpp.online!
@client.get_chats(limit=9999).then { |chats| chats.chat_ids.each do |chat_id| self.process_chat_info(chat_id) end }.wait @client.get_chats(limit=9999).then { |chats| chats.chat_ids.each do |chat_id| self.process_chat_info(chat_id) end }.wait
@logger.info "Contact list updating finished" @logger.info "Contact list updating finished"
@ -96,7 +95,7 @@ class TelegramClient
# message from telegram network handler # # message from telegram network handler #
def message_handler(update) def message_handler(update)
@logger.info 'Got NewMessage update' @logger.debug 'Got NewMessage update'
@logger.debug update.message.to_json @logger.debug update.message.to_json
return if update.message.is_outgoing # ignore outgoing return if update.message.is_outgoing # ignore outgoing
@ -127,14 +126,14 @@ class TelegramClient
# new chat update -- when tg client discovers new chat # # new chat update -- when tg client discovers new chat #
def new_chat_handler(update) def new_chat_handler(update)
@logger.info 'Got NewChat update' @logger.debug 'Got NewChat update'
@logger.debug update.to_json @logger.debug update.to_json
self.process_chat_info(update.chat.id) self.process_chat_info(update.chat.id)
end end
# edited msg # # edited msg #
def message_edited_handler(update) def message_edited_handler(update)
@logger.info 'Got MessageEdited update' @logger.debug 'Got MessageEdited update'
@logger.debug update.to_json @logger.debug update.to_json
# formatting # formatting
@ -144,7 +143,7 @@ class TelegramClient
# deleted msg # # deleted msg #
def message_deleted_handler(update) def message_deleted_handler(update)
@logger.info 'Got MessageDeleted update' @logger.debug 'Got MessageDeleted update'
@logger.debug update.to_json @logger.debug update.to_json
return if not update.is_permanent return if not update.is_permanent
text = "[MSG ID %s DELETE]" % update.message_ids.join(',') text = "[MSG ID %s DELETE]" % update.message_ids.join(',')
@ -153,7 +152,7 @@ class TelegramClient
# file msg -- symlink to download path # # file msg -- symlink to download path #
def file_handler(update) def file_handler(update)
@logger.info 'Got File update' @logger.debug 'Got File update'
@logger.debug update.to_json @logger.debug update.to_json
if update.file.local.is_downloading_completed then if update.file.local.is_downloading_completed then
fname = update.file.local.path.to_s fname = update.file.local.path.to_s
@ -165,7 +164,7 @@ class TelegramClient
# status update handler # # status update handler #
def status_update_handler(update) def status_update_handler(update)
@logger.info 'Got new StatusUpdate' @logger.debug 'Got new StatusUpdate'
@logger.debug update.to_json @logger.debug update.to_json
presence, message = self.format_status(update.status) presence, message = self.format_status(update.status)
@xmpp.presence_update(update.user_id.to_s, presence, message) @xmpp.presence_update(update.user_id.to_s, presence, message)
@ -178,14 +177,15 @@ class TelegramClient
# processing authorization # # processing authorization #
def process_auth(typ, data) def process_auth(typ, data)
@logger.info 'Check authorization :%s..' % typ.to_s @logger.debug 'check_authorization :%s..' % typ.to_s
@client.check_authentication_code(data) if typ == :code @client.check_authentication_code(data) if typ == :code
@client.check_authentication_password(data) if typ == :password @client.check_authentication_password(data) if typ == :password
@xmpp.tg_auth_data = {}
end end
# processing outgoing message from queue # # processing outgoing message from queue #
def process_outgoing_msg(msg) def process_outgoing_msg(msg)
@logger.info 'Sending message to user/chat <%s> within Telegram network..' % msg[:to] @logger.debug 'Sending message to user/chat <%s> within Telegram network..' % msg[:to]
chat_id, text, reply_to = msg[:to].to_i, msg[:text], 0 chat_id, text, reply_to = msg[:to].to_i, msg[:text], 0
# handling replies # # handling replies #
@ -209,7 +209,7 @@ class TelegramClient
# update users information and save it to cache # # update users information and save it to cache #
def process_chat_info(chat_id) def process_chat_info(chat_id)
@logger.info 'Updating chat id %s..' % chat_id.to_s @logger.debug 'Updating chat id %s..' % chat_id.to_s
# fullfil cache.. pasha durov, privet. # # fullfil cache.. pasha durov, privet. #
@client.get_chat(chat_id).then { |chat| @client.get_chat(chat_id).then { |chat|
@ -219,7 +219,7 @@ class TelegramClient
# send to roster # # send to roster #
if @cache[:chats].key? chat_id if @cache[:chats].key? chat_id
@logger.info "Sending presence to roster.." @logger.debug "Sending presence to roster.."
@xmpp.subscription_req(chat_id.to_s, @cache[:chats][chat_id].title.to_s) # send subscription request @xmpp.subscription_req(chat_id.to_s, @cache[:chats][chat_id].title.to_s) # send subscription request
case @cache[:chats][chat_id].type # determine status / presence case @cache[:chats][chat_id].type # determine status / presence
when TD::Types::ChatType::BasicGroup, TD::Types::ChatType::Supergroup then presence, status = :chat, @cache[:chats][chat_id].title.to_s when TD::Types::ChatType::BasicGroup, TD::Types::ChatType::Supergroup then presence, status = :chat, @cache[:chats][chat_id].title.to_s
@ -231,7 +231,7 @@ class TelegramClient
# update user info # # update user info #
def process_user_info(user_id) def process_user_info(user_id)
@logger.info 'Updating user id %s..' % user_id.to_s @logger.debug 'Updating user id %s..' % user_id.to_s
@client.get_user(user_id).then { |user| @cache[:users][user_id] = user }.wait @client.get_user(user_id).then { |user| @cache[:users][user_id] = user }.wait
end end
@ -266,7 +266,7 @@ class TelegramClient
def format_username(user_id) def format_username(user_id)
if not @cache[:users].key? user_id then self.process_user_info(user_id) end if not @cache[:users].key? user_id then self.process_user_info(user_id) end
id = (@cache[:users][user_id].username == '') ? user_id : @cache[:users][user_id].username id = (@cache[:users][user_id].username == '') ? user_id : @cache[:users][user_id].username
name = '%s %s (@%s)' % [@cache[:users][user_id].first_name, @cache[:users][user_id].last_name, @cache[:users][user_id].username] name = '%s %s (@%s)' % [@cache[:users][user_id].first_name, @cache[:users][user_id].last_name, id]
name.sub! ' ]', ']' name.sub! ' ]', ']'
return name return name
end end

View file

@ -1,9 +1,28 @@
require 'xmpp4r' require 'xmpp4r'
require 'sqlite3' require 'sqlite3'
require 'fileutils'
#
# todo: #
#
# last message edit / delete
# join chat / add contact / get information by link/id/@username
# chat admin commands (kick, invite, ban, etc.)
# sending files
# vcards
#
############################# #############################
### Some constants ######### ### Some constants #########
::HELP_MESSAGE = "Unknown command. \n\n Please, use /login <phonenumber> to try log in. ☺" ::HELP_MESSAGE = 'Unknown command.
/login <telegram_login> — Connect to Telegram network
/code 12345 — Enter confirmation code
/password secret — Enter 2FA password
/disconnect ­— Disconnect from Telegram network
/logout — Disconnect from Telegram network and forget session
'
############################# #############################
############################# #############################
@ -13,7 +32,8 @@ class XMPPComponent
# init class and set logger # # init class and set logger #
def initialize(params) def initialize(params)
@logger = Logger.new(STDOUT); @logger.level = params['loglevel'] || Logger::DEBUG; @logger.progname = '[XMPPComponent]' @@loglevel = params['loglevel'] || Logger::DEBUG
@logger = Logger.new(STDOUT); @logger.level = @@loglevel; @logger.progname = '[XMPPComponent]'
@config = { host: params["host"] || 'localhost', port: params["port"] || 8899, jid: params["jid"] || 'tlgrm.rxtx.us', secret: params['secret'] || '' } # default config @config = { host: params["host"] || 'localhost', port: params["port"] || 8899, jid: params["jid"] || 'tlgrm.rxtx.us', secret: params['secret'] || '' } # default config
@sessions = {} @sessions = {}
@db = SQLite3::Database.new(params['db_path'] || 'users.db') @db = SQLite3::Database.new(params['db_path'] || 'users.db')
@ -23,17 +43,17 @@ class XMPPComponent
# database # # database #
def load_db(jid = nil) # load def load_db(jid = nil) # load
@logger.info "Initializing database..." @logger.info "Initializing database.."
query = (jid.nil?) ? "SELECT * FROM users" : "SELECT * FROM users where jid = '%s';" % jid query = (jid.nil?) ? "SELECT * FROM users" : "SELECT * FROM users where jid = '%s';" % jid
@logger.debug(query) @logger.debug(query)
@db.execute(query) do |user| @db.execute(query) do |user|
@logger.info "Found session for JID %s and TG login %s" % [ user["jid"].to_s, user["tg_login"] ] @logger.info "Found session for JID %s and Telegram login %s" % [ user["jid"].to_s, user["tg_login"] ]
@sessions[user["jid"]] = XMPPSession.new(user["jid"], user["tg_login"]) @sessions[user["jid"]] = XMPPSession.new(user["jid"], user["tg_login"])
end end
end end
def update_db(jid, delete = false) # write def update_db(jid, delete = false) # write
return if not @sessions.key? jid return if not @sessions.key? jid
@logger.info "Writing database [add %s].." % jid.to_s @logger.info "Writing database [%s].." % jid.to_s
query = (delete) ? "DELETE FROM users where jid = '%s';" % jid.to_s : "INSERT OR REPLACE INTO users(jid, tg_login) VALUES('%s', '%s');" % [jid.to_s, @sessions[jid].tg_login.to_s] query = (delete) ? "DELETE FROM users where jid = '%s';" % jid.to_s : "INSERT OR REPLACE INTO users(jid, tg_login) VALUES('%s', '%s');" % [jid.to_s, @sessions[jid].tg_login.to_s]
@logger.debug query @logger.debug query
@db.execute(query) @db.execute(query)
@ -49,12 +69,12 @@ class XMPPComponent
@@transport.auth( @config[:secret] ) @@transport.auth( @config[:secret] )
@@transport.add_message_callback do |msg| msg.first_element_text('body') ? self.message_handler(msg) : nil end @@transport.add_message_callback do |msg| msg.first_element_text('body') ? self.message_handler(msg) : nil end
@@transport.add_presence_callback do |presence| self.presence_handler(presence) end @@transport.add_presence_callback do |presence| self.presence_handler(presence) end
@@transport.add_iq_callback do |iq| self.iq_handler(iq) end # @@transport.add_iq_callback do |iq| self.iq_handler(iq) end
@logger.info "Connection established" @logger.info "Connection established"
self.load_db() self.load_db()
@logger.info 'Found %s sessions in database.' % @sessions.count @logger.info 'Found %s sessions in database.' % @sessions.count
@sessions.each do |jid, session| @sessions.each do |jid, session|
@logger.info "Sending presence to %s" % jid @logger.debug "Sending presence to %s" % jid
p = Jabber::Presence.new() p = Jabber::Presence.new()
p.to = jid p.to = jid
p.from = @@transport.jid p.from = @@transport.jid
@ -64,7 +84,7 @@ class XMPPComponent
end end
Thread.stop() Thread.stop()
rescue Exception => e rescue Exception => e
@logger.info 'Connection failed: %s' % e @logger.error 'Connection failed: %s' % e
@db.close @db.close
exit 1 exit 1
end end
@ -82,7 +102,7 @@ class XMPPComponent
end end
def presence_handler(presence) def presence_handler(presence)
@logger.info "New presence iq received" @logger.debug "New presence iq received"
@logger.debug(presence) @logger.debug(presence)
if presence.type == :subscribe then reply = presence.answer(false); reply.type = :subscribed; @@transport.send(reply); end # send "subscribed" reply to "subscribe" presence if presence.type == :subscribe then reply = presence.answer(false); reply.type = :subscribed; @@transport.send(reply); end # send "subscribed" reply to "subscribe" presence
if presence.to == @@transport.jid and @sessions.key? presence.from.bare.to_s and presence.type == :unavailable then @sessions[presence.from.bare.to_s].offline!; return; end # go offline when received offline presence from jabber user if presence.to == @@transport.jid and @sessions.key? presence.from.bare.to_s and presence.type == :unavailable then @sessions[presence.from.bare.to_s].offline!; return; end # go offline when received offline presence from jabber user
@ -90,7 +110,7 @@ class XMPPComponent
end end
def iq_handler(iq) def iq_handler(iq)
@logger.info "New iq received" @logger.debug "New iq received"
@logger.debug(iq) @logger.debug(iq)
end end
@ -115,6 +135,7 @@ class XMPPComponent
@sessions[jfrom].offline! if @sessions.key? jfrom @sessions[jfrom].offline! if @sessions.key? jfrom
self.update_db(jfrom, true) self.update_db(jfrom, true)
@sessions.delete(jfrom) @sessions.delete(jfrom)
FileUtils.remove_dir('sessions/' + jfrom, true)
else # unknown command -- display help # else # unknown command -- display help #
msg = Jabber::Message.new msg = Jabber::Message.new
msg.from = @@transport.jid msg.from = @@transport.jid
@ -131,21 +152,22 @@ end
## XMPP Session Class ####### ## XMPP Session Class #######
############################# #############################
class XMPPSession < XMPPComponent class XMPPSession < XMPPComponent
attr_reader :user_jid, :tg_login, :tg_auth_data, :message_queue attr_reader :user_jid, :tg_login, :message_queue
attr_accessor :online attr_accessor :online, :tg_auth_data
# start XMPP user session and Telegram client instance # # start XMPP user session and Telegram client instance #
def initialize(jid, tg_login) def initialize(jid, tg_login)
@logger = Logger.new(STDOUT); @logger.progname = '[XMPPSession: %s/%s]' % [jid, tg_login] # init logger @logger = Logger.new(STDOUT); @logger.level = @@loglevel; @logger.progname = '[XMPPSession: %s/%s]' % [jid, tg_login] # init logger
@logger.info "Initializing new XMPPSession..." @logger.info "Initializing new session.."
@user_jid, @tg_login, @tg_auth_data, @message_queue = jid, tg_login, {code: nil, password: nil}, Queue.new() # init class variables @user_jid, @tg_login, @tg_auth_data, @message_queue = jid, tg_login, {code: nil, password: nil}, Queue.new() # init class variables
end end
# connect to tg # # connect to tg #
def connect() def connect()
return if self.online? return if self.online?
@logger.info "Starting Telegram session" @logger.info "Spawning Telegram client.."
@online = nil @online = nil
Thread.kill(@telegram_thr) if defined? @telegram_thr # kill old thread if it exists
@telegram_thr = Thread.new{ TelegramClient.new(self, @tg_login) } # init tg instance in new thread @telegram_thr = Thread.new{ TelegramClient.new(self, @tg_login) } # init tg instance in new thread
end end
@ -153,7 +175,7 @@ class XMPPSession < XMPPComponent
# send message to current user via XMPP # # send message to current user via XMPP #
def send_message(from = nil, body = '') def send_message(from = nil, body = '')
@logger.info "Incoming message from Telegram network <- %s" % from.to_s @logger.info "Received new message from Telegram peer %s" % from || "[self]"
reply = Jabber::Message.new reply = Jabber::Message.new
reply.type = :chat reply.type = :chat
reply.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s reply.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s
@ -165,7 +187,7 @@ class XMPPSession < XMPPComponent
# subscription request to current user via XMPP # # subscription request to current user via XMPP #
def subscription_req(from, nickname = nil) def subscription_req(from, nickname = nil)
@logger.info "Subscription request from %s.." %from.to_s @logger.debug "Subscription request from %s.." %from.to_s
req = Jabber::Presence.new() req = Jabber::Presence.new()
req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from> req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from>
req.to = @user_jid # presence <to> req.to = @user_jid # presence <to>
@ -177,7 +199,7 @@ class XMPPSession < XMPPComponent
# presence update # # presence update #
def presence_update(from, status, message, type = nil) def presence_update(from, status, message, type = nil)
@logger.info "Presence update request from %s.." %from.to_s @logger.debug "Presence update request from %s.." %from.to_s
req = Jabber::Presence.new() req = Jabber::Presence.new()
req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from> req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from>
req.to = @user_jid # presence <to> req.to = @user_jid # presence <to>
@ -192,13 +214,13 @@ class XMPPSession < XMPPComponent
# queue message (we will share this queue within :message_queue to Telegram client thread) # # queue message (we will share this queue within :message_queue to Telegram client thread) #
def queue_message(to, text = '') def queue_message(to, text = '')
@logger.info "Queuing message to be sent to Telegram network user -> " % to @logger.debug "Queuing message to be sent to Telegram network user -> " % to
@message_queue << {to: to.split('@')[0], text: text} @message_queue << {to: to.split('@')[0], text: text}
end end
# enter auth data (we will share this data within :tg_auth_data to Telegram client thread ) # # enter auth data (we will share this data within :tg_auth_data to Telegram client thread ) #
def enter_auth_data(typ, data) def enter_auth_data(typ, data)
@logger.info "Authorizing in Telegram network with :%s" % typ @logger.info "Authenticating in Telegram network with :%s" % typ
@tg_auth_data[typ.to_sym] = data @tg_auth_data[typ.to_sym] = data
end end
@ -206,6 +228,6 @@ class XMPPSession < XMPPComponent
# session status # # session status #
def online?() @online end def online?() @online end
def online!() @online = true; @tg_auth = {}; self.subscription_req(nil); self.presence_update(nil, nil, "Logged in as " + @tg_login.to_s) end def online!() @logger.info "Connection established"; @online = true; self.subscription_req(nil); self.presence_update(nil, nil, "Logged in as " + @tg_login.to_s) end
def offline!() @online = false; self.presence_update(nil, nil, "Logged out", :unavailable); end def offline!() @online = false; self.presence_update(nil, nil, "Logged out", :unavailable); end
end end

View file

@ -3,6 +3,7 @@ require 'yaml'
require 'logger' require 'logger'
require 'xmpp4r' require 'xmpp4r'
require 'digest' require 'digest'
require 'fileutils'
require 'sqlite3' require 'sqlite3'
require 'tdlib-ruby' require 'tdlib-ruby'
require_relative 'inc/telegramclient' require_relative 'inc/telegramclient'