Release v0.1
[+] New config options (see config.yml.example) [+] Storing transport sessions in sqlite db [+] Auto connecting if we have stored session JID that came online [+] Auto disconnecting when JID going offline [+] Roster and status synchronisation with Telegram contact list [+] Added Edited and Deleted Messages support [+] Add Files (Audio, Photo, Documents, Stickers) support [+] Added incoming text formatting with MSG ID, user names and login [~] Some new config options (see config.yml.example) [~] Some code refactoring
This commit is contained in:
parent
3b927c00f5
commit
857e8bd202
|
@ -2,12 +2,19 @@ telegram:
|
||||||
|
|
||||||
api_id: '17349'
|
api_id: '17349'
|
||||||
api_hash: '344583e45741c457fe1862106095a5eb'
|
api_hash: '344583e45741c457fe1862106095a5eb'
|
||||||
verbosity: 3
|
verbosity: 2
|
||||||
|
useragent: 'Zhabogram XMPP Gateway'
|
||||||
|
version: '0.10'
|
||||||
|
use_test_dc: false
|
||||||
|
loglevel: 0
|
||||||
|
content_path: '/var/www/tg_media'
|
||||||
|
content_link: 'https://localhost/tg_media'
|
||||||
|
|
||||||
xmpp:
|
xmpp:
|
||||||
|
|
||||||
jid: 'zhabogram.localhost'
|
db_path: 'users.db'
|
||||||
host: '127.0.0.1'
|
jid: 'tlgrm.localhost'
|
||||||
|
host: 'localhost'
|
||||||
port: 8899
|
port: 8899
|
||||||
secret: 'password'
|
secret: 'password'
|
||||||
|
loglevel: 0
|
||||||
|
|
|
@ -1,30 +1,46 @@
|
||||||
require 'tdlib-ruby'
|
require 'tdlib-ruby'
|
||||||
|
require 'digest'
|
||||||
|
|
||||||
class TelegramClient
|
class TelegramClient
|
||||||
|
|
||||||
# tdlib configuration, shared within all instances #
|
# tdlib configuration, shared within all instances #
|
||||||
def self.configure(params)
|
def self.configure(params)
|
||||||
|
@@loglevel = params['loglevel'] || Logger::DEBUG
|
||||||
|
@@content_path = params['content_path'] || '/tmp'
|
||||||
|
@@content_link = params['content_link'] || 'https://localhost/tg_media'
|
||||||
|
@@content_size_limit = params["content_size_limit"] || 100 * 1024 * 1024
|
||||||
TD.configure do |config|
|
TD.configure do |config|
|
||||||
config.lib_path = params[:lib_path] || 'lib/'
|
config.lib_path = params['path'] || 'lib/' # we hope it's here
|
||||||
config.client.api_id = params[:api_id] || '17349' # desktop telegram app
|
config.client.api_id = params['api_id'] || '17349' # desktop telegram app
|
||||||
config.client.api_hash = params[:api_hash] || '344583e45741c457fe1862106095a5eb' # desktop telegram app
|
config.client.api_hash = params['api_hash'] || '344583e45741c457fe1862106095a5eb' # desktop telegram app
|
||||||
|
config.client.device_model = params['useragent'] || 'Zhabogram XMPP Gateway'
|
||||||
|
config.client.application_version = params['version'] || '-1.0' # hmm...
|
||||||
|
config.client.use_test_dc = params['use_test_dc'] || false
|
||||||
|
config.client.system_version = '42' # I think I have permission to hardcode The Ultimate Question of Life, the Universe, and Everything?..
|
||||||
end
|
end
|
||||||
TD::Api.set_log_verbosity_level(params[:verbosity] || 1)
|
TD::Api.set_log_verbosity_level(params['verbosity'] || 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
# instance initialization #
|
# instance initialization #
|
||||||
def initialize(xmpp, login)
|
def initialize(xmpp, login)
|
||||||
|
return if not @@loglevel # call .configure() first
|
||||||
|
|
||||||
@logger = Logger.new(STDOUT); @logger.progname = '[TelegramClient: %s/%s]' % [xmpp.user_jid, login] # create logger
|
@logger = Logger.new(STDOUT); @logger.level = @@loglevel; @logger.progname = '[TelegramClient: %s/%s]' % [xmpp.user_jid, login] # create logger
|
||||||
@xmpp = xmpp # our XMPP user session. we will send messages back to Jabber through this instance.
|
@xmpp = xmpp # our XMPP user session. we will send messages back to Jabber through this instance.
|
||||||
@login = login # store tg login
|
@login = login # store tg login
|
||||||
|
@cache = {chats: {}, users: {} } # we will store our cache here
|
||||||
|
@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 'Spawning Telegram client instance..'
|
||||||
@client = TD::Client.new(database_directory: 'sessions/' + @login, files_directory: 'sessions/' + @login + '/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
|
||||||
|
@client.on(TD::Types::Update::MessageContent) do |update| self.message_edited_handler(update) end # register msg edited handler
|
||||||
|
@client.on(TD::Types::Update::DeleteMessages) do |update| self.message_deleted_handler(update) end # register msg del handler
|
||||||
|
@client.on(TD::Types::Update::File) do |update| self.file_handler(update) end # register file handler
|
||||||
|
@client.on(TD::Types::Update::NewChat) do |update| self.new_chat_handler(update) end # register new chat handler
|
||||||
|
@client.on(TD::Types::Update::UserStatus) do |update| self.status_update_handler(update) end # register status handler
|
||||||
@client.connect #
|
@client.connect #
|
||||||
|
|
||||||
# we will check for outgoing messages in a queue and/or auth data from XMPP thread while XMPP indicates that service is online #
|
# we will check for outgoing messages in a queue and/or auth data from XMPP thread while XMPP indicates that service is online #
|
||||||
|
@ -35,6 +51,8 @@ class TelegramClient
|
||||||
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.5
|
||||||
end
|
end
|
||||||
|
rescue Exception => e
|
||||||
|
@logger.error 'Unexcepted exception! %s' % e.to_s
|
||||||
ensure
|
ensure
|
||||||
@logger.info 'Exitting gracefully...'
|
@logger.info 'Exitting gracefully...'
|
||||||
@client.dispose
|
@client.dispose
|
||||||
|
@ -62,32 +80,106 @@ class TelegramClient
|
||||||
when TD::Types::AuthorizationState::WaitPassword
|
when TD::Types::AuthorizationState::WaitPassword
|
||||||
@logger.info 'Waiting for 2FA password..'
|
@logger.info 'Waiting for 2FA password..'
|
||||||
@xmpp.send_message(nil, 'Please, enter 2FA passphrase via /password 12345')
|
@xmpp.send_message(nil, 'Please, enter 2FA passphrase via /password 12345')
|
||||||
# authorizatio successful #
|
# 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.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
|
||||||
|
@logger.info "Contact list updating finished"
|
||||||
|
self.sync_roster()
|
||||||
|
when TD::Types::AuthorizationState::Closed
|
||||||
|
@logger.info 'Session closed.'
|
||||||
|
@xmpp.offline!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# message from telegram network handler #
|
# message from telegram network handler #
|
||||||
def message_handler(update)
|
def message_handler(update)
|
||||||
@logger.info 'Got NewMessage update'
|
@logger.info 'Got NewMessage update'
|
||||||
from = update.message.chat_id.to_s
|
@logger.debug update.message.to_json
|
||||||
text = update.message.content.text.text.to_s
|
|
||||||
@xmpp.send_message(from, text) if not update.message.is_outgoing
|
return if update.message.is_outgoing # ignore outgoing
|
||||||
|
return if not @cache[:chats].key? update.message.chat_id
|
||||||
|
|
||||||
|
# media? #
|
||||||
|
content = nil
|
||||||
|
@logger.debug update.message.content.to_json
|
||||||
|
case update.message.content # content = [content, name, mime]
|
||||||
|
when TD::Types::MessageContent::Photo then content = [update.message.content.photo.sizes[-1].photo, update.message.content.photo.id.to_s + '.jpg', 'image/jpeg']
|
||||||
|
when TD::Types::MessageContent::Sticker then content = [update.message.content.sticker.sticker, update.message.content.sticker.emoji.to_s + '.webp', 'image/webp']
|
||||||
|
when TD::Types::MessageContent::Audio then content = [update.message.content.audio.audio, update.message.content.audio.file_name.to_s, update.message.content.audio.mime_type.to_s]
|
||||||
|
when TD::Types::MessageContent::Document then content = [update.message.content.document.document, update.message.content.document.file_name.to_s, update.message.content.document.mime_type.to_s]
|
||||||
|
end
|
||||||
|
puts content
|
||||||
|
@client.download_file(content[0].id) if content # download it if already not
|
||||||
|
|
||||||
|
# formatting...
|
||||||
|
text = (content.nil?) ? update.message.content.text.text.to_s : ''
|
||||||
|
text = "[%s (%s), %d bytes] | %s | %s" % [content[1], content[2], content[0].size.to_i, self.format_content_link(content[0].id, content[1]), text] if content # content format
|
||||||
|
text = "[FWD From %s] %s" % [self.format_username(update.message.forward_info.sender_user_id), text] if update.message.forward_info.instance_of? TD::Types::MessageForwardInfo::MessageForwardedFromUser # fwd
|
||||||
|
text = "[Reply to MSG %s] %s" % [update.message.reply_to_message_id.to_s, text] if update.message.reply_to_message_id.to_i != 0 # reply
|
||||||
|
text = "[MSG %s] [%s] %s" % [update.message.id.to_s, self.format_username(update.message.sender_user_id), text] # username/id
|
||||||
|
|
||||||
|
@xmpp.send_message(update.message.chat_id.to_s, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# new chat update -- when tg client discovers new chat #
|
||||||
|
def new_chat_handler(update)
|
||||||
|
@logger.info 'Got NewChat update'
|
||||||
|
@logger.debug update.to_json
|
||||||
|
self.process_chat_info(update.chat.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
# edited msg #
|
||||||
|
def message_edited_handler(update)
|
||||||
|
@logger.info 'Got MessageEdited update'
|
||||||
|
@logger.debug update.to_json
|
||||||
|
|
||||||
|
# formatting
|
||||||
|
text = "[MSG %s EDIT] %s" % [update.message_id.to_s, update.new_content.text.text.to_s]
|
||||||
|
@xmpp.send_message(update.chat_id.to_s, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
# deleted msg #
|
||||||
|
def message_deleted_handler(update)
|
||||||
|
@logger.info 'Got MessageDeleted update'
|
||||||
|
@logger.debug update.to_json
|
||||||
|
return if not update.is_permanent
|
||||||
|
text = "[MSG ID %s DELETE]" % update.message_ids.join(',')
|
||||||
|
@xmpp.send_message(update.chat_id.to_s, text)
|
||||||
|
end
|
||||||
|
|
||||||
|
# file msg -- symlink to download path #
|
||||||
|
def file_handler(update)
|
||||||
|
@logger.info 'Got File update'
|
||||||
|
@logger.debug update.to_json
|
||||||
|
if update.file.local.is_downloading_completed then
|
||||||
|
fname = update.file.local.path.to_s
|
||||||
|
target = "%s/%s%s" % [@@content_path, Digest::SHA256.hexdigest("Current user = %s, File ID = %s" % [@tg_login.to_s, update.file.id.to_s]), File.extname(fname)]
|
||||||
|
@logger.debug 'Downloading of <%s> completed! Link to <%s>' % [fname, target]
|
||||||
|
File.symlink(fname, target)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# status update handler #
|
||||||
|
def status_update_handler(update)
|
||||||
|
@logger.info 'Got new StatusUpdate'
|
||||||
|
@logger.debug update.to_json
|
||||||
|
presence, message = self.format_status(update.status)
|
||||||
|
@xmpp.presence_update(update.user_id.to_s, presence, message)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
###########################################
|
###########################################
|
||||||
## LooP handlers #########################
|
## LooP handlers #########################
|
||||||
###########################################
|
###########################################
|
||||||
|
|
||||||
# processing authorization #
|
# processing authorization #
|
||||||
def process_auth(typ, data)
|
def process_auth(typ, data)
|
||||||
@logger.info 'Check authorization :%s..' % typ.to_s
|
@logger.info 'Check authorization :%s [%s]..' % [typ.to_s, data]
|
||||||
@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 = {} # unset it to prevent extracting 2fa password from memory
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# processing outgoing message from queue #
|
# processing outgoing message from queue #
|
||||||
|
@ -97,4 +189,72 @@ class TelegramClient
|
||||||
@client.send_message(msg[:to].to_i, message)
|
@client.send_message(msg[:to].to_i, message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# update users information and save it to cache #
|
||||||
|
def process_chat_info(chat_id)
|
||||||
|
@logger.info 'Updating chat id %s..' % chat_id.to_s
|
||||||
|
|
||||||
|
# fullfil cache.. pasha durov, privet. #
|
||||||
|
@client.get_chat(chat_id).then { |chat|
|
||||||
|
@cache[:chats][chat_id] = chat # cache chat
|
||||||
|
self.process_user_info(chat.type.user_id) if chat.type.instance_of? TD::Types::ChatType::Private # cache user if it is private chat
|
||||||
|
}.wait
|
||||||
|
|
||||||
|
# send to roster #
|
||||||
|
if @cache[:chats].key? chat_id
|
||||||
|
@logger.info "Sending presence to roster.."
|
||||||
|
@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
|
||||||
|
when TD::Types::ChatType::BasicGroup, TD::Types::ChatType::Supergroup then presence, status = :chat, @cache[:chats][chat_id].title.to_s
|
||||||
|
when TD::Types::ChatType::Private then presence, status = self.format_status(@cache[:users][chat_id].status)
|
||||||
|
end
|
||||||
|
@xmpp.presence_update(chat_id.to_s, presence, status) # send presence
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# update user info #
|
||||||
|
def process_user_info(user_id)
|
||||||
|
@logger.info 'Updating user id %s..' % user_id.to_s
|
||||||
|
@client.get_user(user_id).then { |user| @cache[:users][user_id] = user }.wait
|
||||||
|
end
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
## Format functions #######################
|
||||||
|
###########################################
|
||||||
|
|
||||||
|
# convert telegram status to XMPP one
|
||||||
|
def format_status(status)
|
||||||
|
presence, message = nil, ''
|
||||||
|
case status
|
||||||
|
when TD::Types::UserStatus::Online
|
||||||
|
presence = nil
|
||||||
|
message = "Online"
|
||||||
|
when TD::Types::UserStatus::Offline
|
||||||
|
presence = (Time.now.getutc.to_i - status.was_online.to_i < 3600) ? :away : :xa
|
||||||
|
message = DateTime.strptime(status.was_online.to_s,'%s').strftime("Last seen at %H:%M %d/%m/%Y")
|
||||||
|
when TD::Types::UserStatus::Recently
|
||||||
|
presence = :dnd
|
||||||
|
message = "Last seen recently"
|
||||||
|
when TD::Types::UserStatus::LastWeek
|
||||||
|
presence = :unavailable
|
||||||
|
message = "Last seen last week"
|
||||||
|
when TD::Types::UserStatus::LastMonth
|
||||||
|
presence = :unavailable
|
||||||
|
message = "Last seen last month"
|
||||||
|
end
|
||||||
|
return presence, message
|
||||||
|
end
|
||||||
|
|
||||||
|
# format tg user name #
|
||||||
|
def format_username(user_id)
|
||||||
|
if not @cache[:users].key? user_id then self.process_user_info(user_id) end
|
||||||
|
name = '%s %s (@%s)' % [@cache[:users][user_id].first_name, @cache[:users][user_id].last_name, @cache[:users][user_id].username]
|
||||||
|
name.sub! ' ]', ']'
|
||||||
|
return name
|
||||||
|
end
|
||||||
|
|
||||||
|
# format content link #
|
||||||
|
def format_content_link(file_id, fname)
|
||||||
|
path = "%s/%s%s" % [@@content_link, Digest::SHA256.hexdigest("Current user = %s, File ID = %s" % [@tg_login.to_s, file_id.to_s]).to_s, File.extname(fname)]
|
||||||
|
return path
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
require 'xmpp4r'
|
require 'xmpp4r'
|
||||||
|
require 'sqlite3'
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
### Some constants #########
|
### Some constants #########
|
||||||
|
@ -9,24 +10,62 @@ require 'xmpp4r'
|
||||||
## XMPP Transport Class #####
|
## XMPP Transport Class #####
|
||||||
#############################
|
#############################
|
||||||
class XMPPComponent
|
class XMPPComponent
|
||||||
|
|
||||||
# init class and set logger #
|
# init class and set logger #
|
||||||
def initialize()
|
def initialize(params)
|
||||||
@logger = Logger.new(STDOUT); @logger.progname = '[XMPPComponent]'
|
@logger = Logger.new(STDOUT); @logger.level = params['loglevel'] || Logger::DEBUG; @logger.progname = '[XMPPComponent]'
|
||||||
|
@config = { host: params["host"] || 'localhost', port: params["port"] || 8899, jid: params["jid"] || 'tlgrm.rxtx.us', secret: params['secret'] || '' } # default config
|
||||||
|
@sessions = {}
|
||||||
|
@db = SQLite3::Database.new(params['db_path'] || 'users.db')
|
||||||
|
@db.execute("CREATE TABLE IF NOT EXISTS users(jid varchar(256), tg_login varchar(256), PRIMARY KEY(jid) );")
|
||||||
|
@db.results_as_hash = true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# database #
|
||||||
|
def load_db(jid = nil) # load
|
||||||
|
@logger.info "Initializing database..."
|
||||||
|
query = (jid.nil?) ? "SELECT * FROM users" : "SELECT * FROM users where jid = '%s';" % jid
|
||||||
|
@logger.debug(query)
|
||||||
|
@db.execute(query) do |user|
|
||||||
|
@logger.info "Found session for JID %s and TG login %s" % [ user["jid"].to_s, user["tg_login"] ]
|
||||||
|
@sessions[user["jid"]] = XMPPSession.new(user["jid"], user["tg_login"])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
def update_db(jid, delete = false) # write
|
||||||
|
return if not @sessions.key? jid
|
||||||
|
@logger.info "Writing database [add %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]
|
||||||
|
@logger.debug query
|
||||||
|
@db.execute(query)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
# transport initialization & connecting to XMPP server #
|
# transport initialization & connecting to XMPP server #
|
||||||
def connect(params) # :jid => transport_jid, :host => xmpp_server, :port => xmpp_component_port, :secret => xmpp_component_secret
|
def connect() # :jid => transport_jid, :host => xmpp_server, :port => xmpp_component_port, :secret => xmpp_component_secret
|
||||||
@logger.info "Connecting.."
|
@logger.info "Connecting.."
|
||||||
begin
|
begin
|
||||||
@@transport = Jabber::Component.new( params[:jid] )
|
@@transport = Jabber::Component.new( @config[:jid] )
|
||||||
@@transport.connect( params[:host], params[:port] )
|
@@transport.connect( @config[:host], @config[:port] )
|
||||||
@@transport.auth( params[: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
|
||||||
@sessions = {}
|
@@transport.add_presence_callback do |presence| self.presence_handler(presence) end
|
||||||
|
@@transport.add_iq_callback do |iq| self.iq_handler(iq) end
|
||||||
@logger.info "Connection established"
|
@logger.info "Connection established"
|
||||||
|
self.load_db()
|
||||||
|
@logger.info 'Found %s sessions in database.' % @sessions.count
|
||||||
|
@sessions.each do |jid, session|
|
||||||
|
@logger.info "Sending presence to %s" % jid
|
||||||
|
p = Jabber::Presence.new()
|
||||||
|
p.to = jid
|
||||||
|
p.from = @@transport.jid
|
||||||
|
p.type = :subscribe
|
||||||
|
@logger.debug p
|
||||||
|
@@transport.send(p)
|
||||||
|
end
|
||||||
Thread.stop()
|
Thread.stop()
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
@logger.info 'Connection failed: %s' % e
|
@logger.info 'Connection failed: %s' % e
|
||||||
|
@db.close
|
||||||
exit 1
|
exit 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -38,8 +77,21 @@ class XMPPComponent
|
||||||
# new message to XMPP component #
|
# new message to XMPP component #
|
||||||
def message_handler(msg)
|
def message_handler(msg)
|
||||||
@logger.info 'New message from [%s] to [%s]' % [msg.from, msg.to]
|
@logger.info 'New message from [%s] to [%s]' % [msg.from, msg.to]
|
||||||
return self.process_internal_command(msg.from.bare, msg.first_element_text('body') ) if msg.to == @@transport.jid # treat message as internal command if received as transport jid
|
return self.process_internal_command(msg.from.bare.to_s, msg.first_element_text('body') ) if msg.to == @@transport.jid # treat message as internal command if received as transport jid
|
||||||
return @sessions[msg.from.bare].queue_message(msg.to.to_s, msg.first_element_text('body')) if @sessions.key? msg.from.bare and @sessions[msg.from.bare].online? # queue message for processing session is active for jid from
|
return @sessions[msg.from.bare.to_s].queue_message(msg.to.to_s, msg.first_element_text('body')) if @sessions.key? msg.from.bare.to_s and @sessions[msg.from.bare.to_s].online? # queue message for processing session is active for jid from
|
||||||
|
end
|
||||||
|
|
||||||
|
def presence_handler(presence)
|
||||||
|
@logger.info "New presence iq received"
|
||||||
|
@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.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 then @sessions[presence.from.bare.to_s].connect(); return; end # connect if we have session
|
||||||
|
end
|
||||||
|
|
||||||
|
def iq_handler(iq)
|
||||||
|
@logger.info "New iq received"
|
||||||
|
@logger.debug(iq)
|
||||||
end
|
end
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
|
@ -49,10 +101,28 @@ class XMPPComponent
|
||||||
# process internal /command #
|
# process internal /command #
|
||||||
def process_internal_command(jfrom, body)
|
def process_internal_command(jfrom, body)
|
||||||
case body.split[0] # /command argument = [command, argument]
|
case body.split[0] # /command argument = [command, argument]
|
||||||
when '/login' then @sessions[jfrom] = XMPPSession.new(jfrom, body.split[1]) # create new session for jid <jfrom> and spawn tg instance
|
when '/login' # creating new session if not exists and connect if user already has session
|
||||||
when '/code', '/password' then @sessions[jfrom].enter_auth_data(body.split[0][1..8], body.split[1]) if @sessions.key? jfrom # pass auth data to telegram instance
|
puts @sessions
|
||||||
when '/logout' then @sessions[jfrom].offline! if @sessions.key? jfrom # go offline
|
@sessions[jfrom] = XMPPSession.new(jfrom, body.split[1]) if not @sessions.key? jfrom
|
||||||
else reply = Jabber::Message.new; reply.from, reply.to, reply.body, reply.type = @@transport.jid, jfrom, ::HELP_MESSAGE, :chat; @@transport.send(reply) # unknown command -- we will display sort of help message.
|
@sessions[jfrom].connect()
|
||||||
|
self.update_db(jfrom)
|
||||||
|
when '/code', '/password' # pass auth data if we have session
|
||||||
|
typ = body.split[0][1..8]
|
||||||
|
data = body.split[1]
|
||||||
|
@sessions[jfrom].enter_auth_data(typ, data) if @sessions.key? jfrom
|
||||||
|
when '/disconnect' # going offline without destroying a session
|
||||||
|
@sessions[jfrom].offline! if @sessions.key? jfrom
|
||||||
|
when '/logout' # destroying session
|
||||||
|
@sessions[jfrom].offline! if @sessions.key? jfrom
|
||||||
|
self.update_db(jfrom, true)
|
||||||
|
@sessions.delete(jfrom)
|
||||||
|
else # unknown command -- display help #
|
||||||
|
msg = Jabber::Message.new
|
||||||
|
msg.from = @@transport.jid
|
||||||
|
msg.to = jfrom
|
||||||
|
msg.body = ::HELP_MESSAGE
|
||||||
|
msg.type = :chat
|
||||||
|
@@transport.send(msg)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -67,10 +137,18 @@ class XMPPSession < XMPPComponent
|
||||||
|
|
||||||
# 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]
|
@logger = Logger.new(STDOUT); @logger.progname = '[XMPPSession: %s/%s]' % [jid, tg_login] # init logger
|
||||||
|
@logger.info "Initializing new XMPPSession..."
|
||||||
|
@user_jid, @tg_login, @tg_auth_data, @message_queue = jid, tg_login, {code: nil, password: nil}, Queue.new() # init class variables
|
||||||
|
end
|
||||||
|
|
||||||
|
# connect to tg #
|
||||||
|
def connect()
|
||||||
|
return if self.online?
|
||||||
@logger.info "Starting Telegram session"
|
@logger.info "Starting Telegram session"
|
||||||
@user_jid, @tg_login, @tg_auth_data, @message_queue = jid, tg_login, {code: nil, password: nil}, Queue.new()
|
@online = nil
|
||||||
@tg_client_thread = Thread.new{ TelegramClient.new(self, tg_login) }
|
self.subscription_req(nil)
|
||||||
|
@telegram_thr = Thread.new{ TelegramClient.new(self, @tg_login) } # init tg instance in new thread
|
||||||
end
|
end
|
||||||
|
|
||||||
###########################################
|
###########################################
|
||||||
|
@ -78,11 +156,42 @@ 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 "Incoming message from Telegram network <- %s" % from.to_s
|
||||||
from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s
|
reply = Jabber::Message.new
|
||||||
reply = Jabber::Message.new; reply.from, reply.to, reply.body, reply.type = from, @user_jid, body, :chat
|
reply.type = :chat
|
||||||
|
reply.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s
|
||||||
|
reply.to = @user_jid
|
||||||
|
reply.body = body
|
||||||
|
@logger.debug reply
|
||||||
@@transport.send(reply)
|
@@transport.send(reply)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# subscription request to current user via XMPP #
|
||||||
|
def subscription_req(from, nickname = nil)
|
||||||
|
@logger.info "Subscription request from %s.." %from.to_s
|
||||||
|
req = Jabber::Presence.new()
|
||||||
|
req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from>
|
||||||
|
req.to = @user_jid # presence <to>
|
||||||
|
req.type = :subscribe
|
||||||
|
req.add_element('nick', {'xmlns' => 'http://jabber.org/protocol/nick'} ).add_text(nickname) unless nickname.nil?
|
||||||
|
@logger.debug req
|
||||||
|
@@transport.send(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
# presence update #
|
||||||
|
def presence_update(from, status, message, type = nil)
|
||||||
|
@logger.info "Presence update request from %s.." %from.to_s
|
||||||
|
req = Jabber::Presence.new()
|
||||||
|
req.from = from.nil? ? @@transport.jid : from.to_s+'@'+@@transport.jid.to_s # presence <from>
|
||||||
|
req.to = @user_jid # presence <to>
|
||||||
|
req.show = status unless status.nil? # presence <show>
|
||||||
|
req.type = type unless type.nil? # pres. type
|
||||||
|
req.status = message # presence message
|
||||||
|
@logger.debug req
|
||||||
|
@@transport.send(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
###########################################
|
||||||
|
|
||||||
# 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.info "Queuing message to be sent to Telegram network user -> " % to
|
||||||
|
@ -91,7 +200,7 @@ class XMPPSession < XMPPComponent
|
||||||
|
|
||||||
# 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 "Authorizing in Telegram network with :%s" % typ
|
||||||
@tg_auth_data[typ.to_sym] = data
|
@tg_auth_data[typ.to_sym] = data
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -99,6 +208,6 @@ class XMPPSession < XMPPComponent
|
||||||
|
|
||||||
# session status #
|
# session status #
|
||||||
def online?() @online end
|
def online?() @online end
|
||||||
def online!() @online = true end
|
def online!() @online = true; @tg_auth = {}; self.presence_update(nil, nil, "Logged in as " + @tg_login.to_s) end
|
||||||
def offline!() @online = false end
|
def offline!() @online = false; self.presence_update(nil, nil, "Logged out", :unavailable); end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
# Some very important libraries'
|
# Some very important libraries'
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'logger'
|
require 'logger'
|
||||||
|
require 'xmpp4r'
|
||||||
|
require 'digest'
|
||||||
|
require 'sqlite3'
|
||||||
|
require 'tdlib-ruby'
|
||||||
require_relative 'inc/telegramclient'
|
require_relative 'inc/telegramclient'
|
||||||
require_relative 'inc/xmppcomponent'
|
require_relative 'inc/xmppcomponent'
|
||||||
|
|
||||||
|
@ -8,5 +12,5 @@ require_relative 'inc/xmppcomponent'
|
||||||
Config = YAML.load_file(File.dirname(__FILE__) + '/config.yml')
|
Config = YAML.load_file(File.dirname(__FILE__) + '/config.yml')
|
||||||
|
|
||||||
# Configure Telegram Client #
|
# Configure Telegram Client #
|
||||||
TelegramClient.configure(api_id: Config['telegram']['api_id'], api_hash: Config['telegram']['api_hash'], verbosity: Config['telegram']['verbosity'])
|
TelegramClient.configure(Config['telegram'])
|
||||||
XMPPComponent.new().connect(host: Config['xmpp']['host'], port: Config['xmpp']['port'], jid: Config['xmpp']['jid'], secret: Config['xmpp']['secret'])
|
XMPPComponent.new(Config['xmpp']).connect()
|
||||||
|
|
Reference in a new issue