Release 1.0.
[NEW] implemented XEP-0030 (Service Discovery) — you can now see transport in service discovery [NEW] implemented XEP-0077 (In-Band Registration) — now you can also start session from GUI Jabber client [NEW] implemented XEP-0100 iq:jabber:gateway — you can now add Telegram contact from your GUI Jabber client [NEW] implemented presence cache. now sending presences once a minute to prevent presence flooding [FIX] fixed online presence after establishing telegram session and offline after disposing session [FIX] won't use tdlib message database anymore, implemented our own messages cache [FIX] show "chat created" message correctly [FIX] fixed /info command [FIX] fixed closing secret chats [UPD] now you can start secret chat directly by sending /secret to contact [UPD] /debug renamed to /info, now shows active sesions, removed memory profiling as it does not helps [UPD] removed main loop (but still shutting down correctly) — use systemd etc. to restart transport correctly
This commit is contained in:
parent
7904631a83
commit
dc615f977c
|
@ -17,10 +17,10 @@ class TelegramClient
|
||||||
config.client.application_version = params['version'] || '1.0' # hmm...
|
config.client.application_version = params['version'] || '1.0' # hmm...
|
||||||
config.client.use_test_dc = params['use_test_dc'] || false
|
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?..
|
config.client.system_version = '42' # I think I have permission to hardcode The Ultimate Question of Life, the Universe, and Everything?..
|
||||||
config.client.use_file_database = true # wow
|
config.client.use_file_database = false # wow
|
||||||
config.client.use_message_database = true # such library
|
config.client.use_message_database = false # such library
|
||||||
config.client.use_chat_info_database = false # much options
|
config.client.use_chat_info_database = false # much options
|
||||||
config.client.enable_storage_optimizer = true # ...
|
config.client.enable_storage_optimizer = false # ...
|
||||||
end
|
end
|
||||||
TD::Api.set_log_verbosity_level(params['verbosity'] || 1)
|
TD::Api.set_log_verbosity_level(params['verbosity'] || 1)
|
||||||
end
|
end
|
||||||
|
@ -37,7 +37,7 @@ class TelegramClient
|
||||||
@me = nil # self telegram profile
|
@me = nil # self telegram profile
|
||||||
@online = nil # we do not know
|
@online = nil # we do not know
|
||||||
@auth_state = 'nil' # too.
|
@auth_state = 'nil' # too.
|
||||||
@cache = {chats: {}, users: {}, users_fullinfo: {}, userpics: {}, unread_msg: {} } # cache storage
|
@cache = {chats: {}, users: {}, users_fullinfo: {}, userpics: {}, unread_msg: {}, incoming_msg: [] } # cache storage
|
||||||
@files_dir = File.dirname(__FILE__) + '/../sessions/' + @jid + '/files/'
|
@files_dir = File.dirname(__FILE__) + '/../sessions/' + @jid + '/files/'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ class TelegramClient
|
||||||
@cache[:chats].each_key do |chat_id| @xmpp.presence(@jid, chat_id.to_s, :unavailable) end # send offline presences
|
@cache[:chats].each_key do |chat_id| @xmpp.presence(@jid, chat_id.to_s, :unavailable) end # send offline presences
|
||||||
(logout) ? @client.log_out : @client.dispose # logout if needed
|
(logout) ? @client.log_out : @client.dispose # logout if needed
|
||||||
@online = false
|
@online = false
|
||||||
|
@xmpp.presence(@jid, nil, :unavailable)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,9 +96,9 @@ class TelegramClient
|
||||||
@client.get_me().then { |user| @me = user }.wait
|
@client.get_me().then { |user| @me = user }.wait
|
||||||
@client.get_chats(limit=9999)
|
@client.get_chats(limit=9999)
|
||||||
@logger.info "Contact list updating finished"
|
@logger.info "Contact list updating finished"
|
||||||
@online = true
|
|
||||||
@xmpp.presence(@jid, nil, :subscribe)
|
@xmpp.presence(@jid, nil, :subscribe)
|
||||||
@xmpp.presence(@jid, nil, nil, "Logged in as %s" % @login)
|
@xmpp.presence(@jid, nil, nil, nil, "Logged in as %s" % @login)
|
||||||
|
@online = true
|
||||||
# closing session: sent offline presences to XMPP user #
|
# closing session: sent offline presences to XMPP user #
|
||||||
when TD::Types::AuthorizationState::Closing
|
when TD::Types::AuthorizationState::Closing
|
||||||
@logger.info 'Closing session..'
|
@logger.info 'Closing session..'
|
||||||
|
@ -113,11 +114,14 @@ class TelegramClient
|
||||||
def message_handler(update, show_date = false)
|
def message_handler(update, show_date = false)
|
||||||
@logger.debug 'Got NewMessage update'
|
@logger.debug 'Got NewMessage update'
|
||||||
@logger.debug update.message.to_json
|
@logger.debug update.message.to_json
|
||||||
|
|
||||||
@logger.info 'New message from Telegram chat %s' % update.message.chat_id
|
@logger.info 'New message from Telegram chat %s' % update.message.chat_id
|
||||||
|
|
||||||
|
# we need to ignore incoming messages sometime
|
||||||
|
@cache[:incoming_msg].shift(900) if @cache[:incoming_msg].size > 1000 # clean cache if it exceeds 1000 messages (but remain last 100 entires)
|
||||||
|
return if @cache[:incoming_msg].include? update.message.id # ignore message if it's already seen
|
||||||
return if update.message.is_outgoing and update.message.sending_state.instance_of? TD::Types::MessageSendingState::Pending # ignore self outgoing messages
|
return if update.message.is_outgoing and update.message.sending_state.instance_of? TD::Types::MessageSendingState::Pending # ignore self outgoing messages
|
||||||
|
|
||||||
# media? #
|
# media? #
|
||||||
file = nil
|
file = nil
|
||||||
prefix = ''
|
prefix = ''
|
||||||
|
@ -144,6 +148,8 @@ class TelegramClient
|
||||||
when TD::Types::MessageContent::Document # documents
|
when TD::Types::MessageContent::Document # documents
|
||||||
file = update.message.content.document.document
|
file = update.message.content.document.document
|
||||||
text = "%s (%s), %d bytes | %s | %s" % [update.message.content.document.file_name.to_s, update.message.content.document.mime_type.to_s, file.size.to_i, self.format_content_link(file.remote.id, update.message.content.document.file_name.to_s), update.message.content.caption.text.to_s]
|
text = "%s (%s), %d bytes | %s | %s" % [update.message.content.document.file_name.to_s, update.message.content.document.mime_type.to_s, file.size.to_i, self.format_content_link(file.remote.id, update.message.content.document.file_name.to_s), update.message.content.caption.text.to_s]
|
||||||
|
when TD::Types::MessageContent::BasicGroupChatCreate, TD::Types::MessageContent::SupergroupChatCreate # group created
|
||||||
|
text = "created"
|
||||||
when TD::Types::MessageContent::ChatJoinByLink # joined member
|
when TD::Types::MessageContent::ChatJoinByLink # joined member
|
||||||
text = "joined"
|
text = "joined"
|
||||||
when TD::Types::MessageContent::ChatAddMembers # add members
|
when TD::Types::MessageContent::ChatAddMembers # add members
|
||||||
|
@ -177,6 +183,7 @@ class TelegramClient
|
||||||
|
|
||||||
# send and add message id to unreads
|
# send and add message id to unreads
|
||||||
@cache[:unread_msg][update.message.chat_id] = update.message.id
|
@cache[:unread_msg][update.message.chat_id] = update.message.id
|
||||||
|
@cache[:incoming_msg] << update.message.id
|
||||||
@xmpp.message(@jid, update.message.chat_id.to_s, text)
|
@xmpp.message(@jid, update.message.chat_id.to_s, text)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -230,7 +237,7 @@ class TelegramClient
|
||||||
@logger.debug 'Got new StatusUpdate'
|
@logger.debug 'Got new StatusUpdate'
|
||||||
@logger.debug update.to_json
|
@logger.debug update.to_json
|
||||||
return if update.user_id == @me.id # ignore self statuses
|
return if update.user_id == @me.id # ignore self statuses
|
||||||
self.process_status_update(update.user_id, update.status)
|
self.process_status_update(update.user_id, update.status, false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
@ -256,8 +263,10 @@ class TelegramClient
|
||||||
|
|
||||||
case splitted[0]
|
case splitted[0]
|
||||||
when '/info' # information about user / chat
|
when '/info' # information about user / chat
|
||||||
id = splitted[1].to_i
|
|
||||||
response = ''
|
response = ''
|
||||||
|
id = (resolved) ? resolved.id : splitted[1]
|
||||||
|
id ||= chat_id
|
||||||
|
id = id.to_i
|
||||||
self.process_user_info(id) if id and id > 0 and not @cache[:users].key? id
|
self.process_user_info(id) if id and id > 0 and not @cache[:users].key? id
|
||||||
self.process_chat_info(id, false) if id and id < 0 and not @cache[:cache].key? id
|
self.process_chat_info(id, false) if id and id < 0 and not @cache[:cache].key? id
|
||||||
response = self.format_chatname(id) if @cache[:chats].key? id
|
response = self.format_chatname(id) if @cache[:chats].key? id
|
||||||
|
@ -267,9 +276,11 @@ class TelegramClient
|
||||||
self.process_chat_info(chat) if chat != 0
|
self.process_chat_info(chat) if chat != 0
|
||||||
when '/join' # join group/supergroup by invite link or by id
|
when '/join' # join group/supergroup by invite link or by id
|
||||||
chat = (resolved) ? resolved.id : splitted[1]
|
chat = (resolved) ? resolved.id : splitted[1]
|
||||||
|
chat ||= chat_id
|
||||||
chat.to_s[0..3] == "http" ? @client.join_chat_by_invite_link(chat).wait : @client.join_chat(chat.to_i).wait
|
chat.to_s[0..3] == "http" ? @client.join_chat_by_invite_link(chat).wait : @client.join_chat(chat.to_i).wait
|
||||||
when '/secret' # create new secret chat
|
when '/secret' # create new secret chat
|
||||||
@client.create_new_secret_chat(resolved.id) if resolved
|
uid = (resolved) ? resolved.id : chat_id
|
||||||
|
@client.create_new_secret_chat(uid) if uid > 0
|
||||||
when '/group' # create new group with @user_id
|
when '/group' # create new group with @user_id
|
||||||
@client.create_new_basic_group_chat([resolved.id], splitted[2]) if resolved and splitted[2]
|
@client.create_new_basic_group_chat([resolved.id], splitted[2]) if resolved and splitted[2]
|
||||||
when '/supergroup' # create new supergroup
|
when '/supergroup' # create new supergroup
|
||||||
|
@ -303,7 +314,7 @@ class TelegramClient
|
||||||
when '/leave', '/delete' # delete / leave chat
|
when '/leave', '/delete' # delete / leave chat
|
||||||
@client.close_chat(chat_id).wait
|
@client.close_chat(chat_id).wait
|
||||||
@client.leave_chat(chat_id).wait
|
@client.leave_chat(chat_id).wait
|
||||||
@client.close_secret_chat(@cache[:chats][chat_id].secret_chat_id).wait if @cache[:chats][chat_id].type.instance_of? TD::Types::ChatType::Secret
|
@client.close_secret_chat(@cache[:chats][chat_id].type.secret_chat_id).wait if @cache[:chats][chat_id].type.instance_of? TD::Types::ChatType::Secret
|
||||||
@client.delete_chat_history(chat_id, true).wait
|
@client.delete_chat_history(chat_id, true).wait
|
||||||
@xmpp.presence(@jid, chat_id, :unsubscribed)
|
@xmpp.presence(@jid, chat_id, :unsubscribed)
|
||||||
@xmpp.presence(@jid, chat_id, :unavailable)
|
@xmpp.presence(@jid, chat_id, :unavailable)
|
||||||
|
@ -378,7 +389,7 @@ class TelegramClient
|
||||||
'
|
'
|
||||||
end
|
end
|
||||||
|
|
||||||
@xmpp.message(@jid, chat_id, response) if response
|
@xmpp.message(@jid, chat_id.to_s, response) if response
|
||||||
end
|
end
|
||||||
|
|
||||||
# processing outgoing message from queue #
|
# processing outgoing message from queue #
|
||||||
|
@ -439,7 +450,7 @@ class TelegramClient
|
||||||
end
|
end
|
||||||
|
|
||||||
# convert telegram status to XMPP one
|
# convert telegram status to XMPP one
|
||||||
def process_status_update(user_id, status)
|
def process_status_update(user_id, status, immed = true)
|
||||||
@logger.debug "Processing status update for user id %s.." % user_id.to_s
|
@logger.debug "Processing status update for user id %s.." % user_id.to_s
|
||||||
xmpp_show, xmpp_status, xmpp_photo = nil
|
xmpp_show, xmpp_status, xmpp_photo = nil
|
||||||
case status
|
case status
|
||||||
|
@ -460,7 +471,7 @@ class TelegramClient
|
||||||
xmpp_status = "Last seen last month"
|
xmpp_status = "Last seen last month"
|
||||||
end
|
end
|
||||||
xmpp_photo = @cache[:userpics][user_id] if @cache[:userpics].key? user_id
|
xmpp_photo = @cache[:userpics][user_id] if @cache[:userpics].key? user_id
|
||||||
@xmpp.presence(@jid, user_id.to_s, nil, xmpp_show, xmpp_status, nil, xmpp_photo)
|
@xmpp.presence(@jid, user_id.to_s, nil, xmpp_show, xmpp_status, nil, xmpp_photo, immed)
|
||||||
end
|
end
|
||||||
|
|
||||||
# get contact information (for vcard).
|
# get contact information (for vcard).
|
||||||
|
@ -489,6 +500,21 @@ class TelegramClient
|
||||||
return title, username, firstname, lastname, phone, bio, userpic
|
return title, username, firstname, lastname, phone, bio, userpic
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# resolve id by @username (or just return id)
|
||||||
|
def resolve_username(username)
|
||||||
|
resolved = nil
|
||||||
|
if username[0] == '@' then # @username
|
||||||
|
@client.search_public_chat(username[1..-1]).then {|chat| resolved = chat.id}.wait
|
||||||
|
elsif username[0..3] == 'http' or username[0..3] == 't.me' then # chat link
|
||||||
|
@client.join_chat_by_invite_link(username)
|
||||||
|
elsif username.to_i != 0 then # user id
|
||||||
|
resolved = username
|
||||||
|
end
|
||||||
|
|
||||||
|
return '' if not resolved
|
||||||
|
return '@' + resolved.to_s
|
||||||
|
end
|
||||||
|
|
||||||
###########################################
|
###########################################
|
||||||
## Format functions #######################
|
## Format functions #######################
|
||||||
###########################################
|
###########################################
|
||||||
|
|
|
@ -9,9 +9,8 @@
|
||||||
/disconnect — Disconnect from Telegram network
|
/disconnect — Disconnect from Telegram network
|
||||||
/logout — Disconnect from Telegram network and forget session
|
/logout — Disconnect from Telegram network and forget session
|
||||||
|
|
||||||
/sessions — Shows current active sessions (available for admins)
|
/info — Show information and usage statistics of this instance (only for JIDs specified as administrators)
|
||||||
/debug — Shows some debug information (available for admins)
|
/restart — Restart this instance (only for JIDs specified as administrators)
|
||||||
/restart — Reset Zhabogram (available for admins)
|
|
||||||
'
|
'
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
|
@ -19,21 +18,27 @@
|
||||||
#############################
|
#############################
|
||||||
## XMPP Transport Class #####
|
## XMPP Transport Class #####
|
||||||
#############################
|
#############################
|
||||||
|
|
||||||
|
include Jabber::Discovery
|
||||||
|
include Jabber::Dataforms
|
||||||
|
|
||||||
class XMPPComponent
|
class XMPPComponent
|
||||||
|
|
||||||
# init class and set logger #
|
# init class and set logger #
|
||||||
def initialize(params)
|
def initialize(params)
|
||||||
@@loglevel = params['loglevel'] || Logger::DEBUG
|
@@loglevel = params['loglevel'] || Logger::DEBUG
|
||||||
@logger = Logger.new(STDOUT); @logger.level = @@loglevel; @logger.progname = '[XMPPComponent]'
|
@logger = Logger.new(STDOUT); @logger.level = @@loglevel; @logger.progname = '[XMPPComponent]'
|
||||||
@config = { host: params["host"] || 'localhost', port: params["port"] || 8899, jid: params["jid"] || 'tlgrm.localhost', secret: params['password'] || '', admins: params['admins'] || [], debug: params['debug'] || false } # default config
|
@config = { host: params["host"] || 'localhost', port: params["port"] || 8899, jid: params["jid"] || 'tlgrm.localhost', secret: params['password'] || '', admins: params['admins'] || [], debug: params['debug'] } # default config
|
||||||
@sessions = {}
|
@sessions = {}
|
||||||
|
@presence_que = {}
|
||||||
@db = SQLite3::Database.new(params['db_path'] || 'users.db')
|
@db = SQLite3::Database.new(params['db_path'] || 'users.db')
|
||||||
@db.execute("CREATE TABLE IF NOT EXISTS users(jid varchar(256), login varchar(256), PRIMARY KEY(jid) );")
|
@db.execute("CREATE TABLE IF NOT EXISTS users(jid varchar(256), login varchar(256), PRIMARY KEY(jid) );")
|
||||||
@db.results_as_hash = true
|
@db.results_as_hash = true
|
||||||
|
self.load_db()
|
||||||
end
|
end
|
||||||
|
|
||||||
# load sessions from db #
|
# load sessions from db #
|
||||||
def load_db(jid = nil) # load
|
def load_db(jid = nil)
|
||||||
@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)
|
||||||
|
@ -41,34 +46,52 @@ class XMPPComponent
|
||||||
end
|
end
|
||||||
|
|
||||||
# store session to db #
|
# store session to db #
|
||||||
def update_db(jid, delete = false) # write
|
def update_db(jid, delete = false, register = false)
|
||||||
return if not @sessions.key? jid
|
login = (not register and @sessions.key? jid) ? @sessions[jid].login.to_s : register
|
||||||
|
return if not login
|
||||||
@logger.info "Writing database [%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, login) VALUES('%s', '%s');" % [jid.to_s, @sessions[jid].login.to_s]
|
query = (delete) ? "DELETE FROM users where jid = '%s';" % jid.to_s : "INSERT OR REPLACE INTO users(jid, login) VALUES('%s', '%s');" % [jid.to_s, login]
|
||||||
@logger.debug query
|
@logger.debug query
|
||||||
@db.execute(query)
|
@db.execute(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
# connecting to XMPP server #
|
# connecting to XMPP server #
|
||||||
def connect() # :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.."
|
|
||||||
begin
|
begin
|
||||||
Jabber::debug = @config[:debug]
|
Jabber::debug = @config[:debug]
|
||||||
@@transport = Jabber::Component.new( @config[:jid] )
|
|
||||||
@@transport.connect( @config[:host], @config[:port] )
|
# component
|
||||||
@@transport.auth( @config[:secret] )
|
@component = Jabber::Component.new( @config[:jid] )
|
||||||
@@transport.add_message_callback do |msg| msg.first_element_text('body') ? self.message_handler(msg) : nil end
|
@component.connect( @config[:host], @config[:port] )
|
||||||
@@transport.add_presence_callback do |presence| self.presence_handler(presence) end
|
@component.auth( @config[:secret] )
|
||||||
@@transport.add_iq_callback do |iq| self.iq_handler(iq) end
|
@component.add_message_callback do |msg| msg.first_element_text('body') ? self.message_handler(msg) : nil end
|
||||||
@@transport.on_exception do |exception, stream, state| self.survive(exception, stream, state) end
|
@component.add_presence_callback do |presence| self.presence_handler(presence) end
|
||||||
@logger.info "Connection established"
|
@component.add_iq_callback do |iq| self.iq_handler(iq) end
|
||||||
self.load_db()
|
@component.on_exception do |exception, stream, state| self.survive(exception, stream, state) end
|
||||||
@logger.info 'Found %s sessions in database.' % @sessions.count
|
@logger.info "Connection to XMPP server established!"
|
||||||
|
|
||||||
|
# disco
|
||||||
|
@disco = Jabber::Discovery::Responder.new(@component)
|
||||||
|
@disco.identities = [ Identity.new('gateway', 'Telegram Gateway', 'telegram') ]
|
||||||
|
@disco.add_features(['http://jabber.org/protocol/disco','jabber:iq:register'])
|
||||||
|
|
||||||
|
# janbber::iq::register
|
||||||
|
@iq_register = Jabber::Register::Responder.new(@component)
|
||||||
|
@iq_register.instructions = 'Please enter your Telegram login'
|
||||||
|
@iq_register.add_field(:login, true) do |jid, login| self.process_command(jid, '/login %s' % login) end
|
||||||
|
|
||||||
|
# jabber::iq::gateway
|
||||||
|
@iq_gateway = Jabber::Gateway::Responder.new(@component) do |iq, query| (@sessions.key? iq.from.bare.to_s and @sessions[iq.from.bare.to_s].online?) ? @sessions[iq.from.bare.to_s].resolve_username(query).to_s + '@' + @component.jid.to_s : '' end
|
||||||
|
@iq_gateway.description = "Specify @username / ID / https://t.me/link"
|
||||||
|
@iq_gateway.prompt = "Telegram contact"
|
||||||
|
|
||||||
|
@logger.info 'Loaded %s sessions from database.' % @sessions.count
|
||||||
@sessions.each do |jid, session| self.presence(jid, nil, :subscribe) end
|
@sessions.each do |jid, session| self.presence(jid, nil, :subscribe) end
|
||||||
Thread.stop()
|
Thread.new { while @component.is_connected? do @presence_que.each_value { |p| @component.send(p) }; @presence_que.clear; sleep(60); end } # presence updater thread
|
||||||
|
Thread.stop()
|
||||||
rescue Interrupt
|
rescue Interrupt
|
||||||
@logger.error 'Interrupted!'
|
@logger.error 'Interrupted!'
|
||||||
@@transport.on_exception do |exception,| end
|
@component.on_exception do |exception,| end
|
||||||
self.disconnect()
|
self.disconnect()
|
||||||
return -11
|
return -11
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
|
@ -80,35 +103,35 @@ class XMPPComponent
|
||||||
|
|
||||||
# transport shutdown #
|
# transport shutdown #
|
||||||
def disconnect()
|
def disconnect()
|
||||||
@logger.info "Closing all connections..."
|
@logger.info "Closing connections..."
|
||||||
@sessions.each do |jid, session| @sessions[jid].disconnect() end
|
@sessions.each do |jid, session| @sessions[jid].disconnect() end
|
||||||
@@transport.close()
|
@component.close()
|
||||||
end
|
end
|
||||||
|
|
||||||
# vse umrut a ya ostanus'... #
|
# vse umrut a ya ostanus'... #
|
||||||
def survive(exception, stream, state)
|
def survive(exception, stream, state)
|
||||||
@logger.error "Stream error on :%s (%s)" % [state.to_s, exception.to_s]
|
@logger.error "Stream error on :%s (%s)" % [state.to_s, exception.to_s]
|
||||||
@logger.info "Trying to ressurect XMPP stream.."
|
@logger.info "Trying to revive stream.."
|
||||||
self.connect()
|
self.connect()
|
||||||
end
|
end
|
||||||
|
|
||||||
# message to users #
|
# message to users #
|
||||||
def message(to, from = nil, body = '')
|
def message(to, from = nil, body = '')
|
||||||
@logger.info "Sending message from <%s> to <%s>" % [from || @@transport.jid, to]
|
@logger.info "Sending message from <%s> to <%s>" % [from || @component.jid, to]
|
||||||
msg = Jabber::Message.new
|
msg = Jabber::Message.new
|
||||||
msg.from = (from) ? "%s@%s" % [from, @@transport.jid.to_s] : @@transport.jid
|
msg.from = (from) ? "%s@%s" % [from, @component.jid.to_s] : @component.jid
|
||||||
msg.to = to
|
msg.to = to
|
||||||
msg.body = body
|
msg.body = body
|
||||||
msg.type = :chat
|
msg.type = :chat
|
||||||
@logger.debug msg.to_s
|
@logger.debug msg.to_s
|
||||||
@@transport.send(msg)
|
@component.send(msg)
|
||||||
end
|
end
|
||||||
|
|
||||||
# presence update #
|
# presence update #
|
||||||
def presence(to, from = nil, type = nil, show = nil, status = nil, nickname = nil, photo = nil)
|
def presence(to, from = nil, type = nil, show = nil, status = nil, nickname = nil, photo = nil, immediately = true)
|
||||||
@logger.debug "Presence update request from %s.." % from.to_s
|
@logger.debug "Presence update request from %s (immed = %s).." % [from.to_s, immediately]
|
||||||
req = Jabber::Presence.new()
|
req = Jabber::Presence.new()
|
||||||
req.from = from.nil? ? @@transport.jid : "%s@%s" % [from, @@transport.jid] # presence <from>
|
req.from = from.nil? ? @component.jid : "%s@%s" % [from, @component.jid] # presence <from>
|
||||||
req.to = to # presence <to>
|
req.to = to # presence <to>
|
||||||
req.type = type unless type.nil? # pres. type
|
req.type = type unless type.nil? # pres. type
|
||||||
req.show = show unless show.nil? # presence <show>
|
req.show = show unless show.nil? # presence <show>
|
||||||
|
@ -116,7 +139,7 @@ class XMPPComponent
|
||||||
req.add_element('nick', {'xmlns' => 'http://jabber.org/protocol/nick'} ).add_text(nickname) unless nickname.nil? # nickname
|
req.add_element('nick', {'xmlns' => 'http://jabber.org/protocol/nick'} ).add_text(nickname) unless nickname.nil? # nickname
|
||||||
req.add_element('x', {'xmlns' => 'vcard-temp:x:update'} ).add_element("photo").add_text(photo) unless photo.nil? # nickname
|
req.add_element('x', {'xmlns' => 'vcard-temp:x:update'} ).add_element("photo").add_text(photo) unless photo.nil? # nickname
|
||||||
@logger.debug req.to_s
|
@logger.debug req.to_s
|
||||||
@@transport.send(req)
|
(immediately) ? @component.send(req) : @presence_que.store(to, req)
|
||||||
end
|
end
|
||||||
|
|
||||||
# request timezone information #
|
# request timezone information #
|
||||||
|
@ -125,11 +148,11 @@ class XMPPComponent
|
||||||
iq = Jabber::Iq.new
|
iq = Jabber::Iq.new
|
||||||
iq.type = :get
|
iq.type = :get
|
||||||
iq.to = jid
|
iq.to = jid
|
||||||
iq.from = @@transport.jid
|
iq.from = @component.jid
|
||||||
iq.id = 'time_req_1'
|
iq.id = 'time_req_1'
|
||||||
iq.add_element("time", {"xmlns" => "urn:xmpp:time"})
|
iq.add_element("time", {"xmlns" => "urn:xmpp:time"})
|
||||||
@logger.debug iq.to_s
|
@logger.debug iq.to_s
|
||||||
@@transport.send(iq)
|
@component.send(iq)
|
||||||
end
|
end
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
|
@ -141,7 +164,7 @@ class XMPPComponent
|
||||||
return if msg.type == :error
|
return if msg.type == :error
|
||||||
@logger.info 'Received message from <%s> to <%s>' % [msg.from.to_s, msg.to.to_s]
|
@logger.info 'Received message from <%s> to <%s>' % [msg.from.to_s, msg.to.to_s]
|
||||||
@logger.debug msg.to_s
|
@logger.debug msg.to_s
|
||||||
if msg.to == @@transport.jid then self.process_command(msg.from, msg.first_element_text('body') ); return; end # treat message as internal command if received as transport jid
|
if msg.to == @component.jid then self.process_command(msg.from, msg.first_element_text('body') ); return; end # treat message as internal command if received as transport jid
|
||||||
if @sessions.key? msg.from.bare.to_s then self.request_tz(msg.from) if not @sessions[msg.from.bare.to_s].tz_set?; @sessions[msg.from.bare.to_s].process_outgoing_msg(msg.to.to_s.split('@')[0].to_i, msg.first_element_text('body')); return; end #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
|
if @sessions.key? msg.from.bare.to_s then self.request_tz(msg.from) if not @sessions[msg.from.bare.to_s].tz_set?; @sessions[msg.from.bare.to_s].process_outgoing_msg(msg.to.to_s.split('@')[0].to_i, msg.first_element_text('body')); return; end #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
|
end
|
||||||
|
|
||||||
|
@ -149,9 +172,9 @@ class XMPPComponent
|
||||||
def presence_handler(prsnc)
|
def presence_handler(prsnc)
|
||||||
@logger.debug "Received presence :%s from <%s> to <%s>" % [prsnc.type.to_s, prsnc.from.to_s, prsnc.to.to_s]
|
@logger.debug "Received presence :%s from <%s> to <%s>" % [prsnc.type.to_s, prsnc.from.to_s, prsnc.to.to_s]
|
||||||
@logger.debug(prsnc.to_s)
|
@logger.debug(prsnc.to_s)
|
||||||
if prsnc.type == :subscribe then reply = prsnc.answer(false); reply.type = :subscribed; @@transport.send(reply); end # send "subscribed" reply to "subscribe" presence
|
if prsnc.type == :subscribe then reply = prsnc.answer(false); reply.type = :subscribed; @component.send(reply); end # send "subscribed" reply to "subscribe" presence
|
||||||
if prsnc.to == @@transport.jid and @sessions.key? prsnc.from.bare.to_s and prsnc.type == :unavailable then @sessions[prsnc.from.bare.to_s].disconnect(); return; end # go offline when received offline presence from jabber user
|
if prsnc.to == @component.jid and @sessions.key? prsnc.from.bare.to_s and prsnc.type == :unavailable then @sessions[prsnc.from.bare.to_s].disconnect(); return; end # go offline when received offline presence from jabber user
|
||||||
if prsnc.to == @@transport.jid and @sessions.key? prsnc.from.bare.to_s then self.request_tz(prsnc.from); @sessions[prsnc.from.bare.to_s].connect(); return; end # connect if we have session
|
if prsnc.to == @component.jid and @sessions.key? prsnc.from.bare.to_s then self.request_tz(prsnc.from); @sessions[prsnc.from.bare.to_s].connect(); return; end # connect if we have session
|
||||||
end
|
end
|
||||||
|
|
||||||
# new iq (vcard/tz) request to XMPP component #
|
# new iq (vcard/tz) request to XMPP component #
|
||||||
|
@ -177,7 +200,7 @@ class XMPPComponent
|
||||||
reply.type = :result
|
reply.type = :result
|
||||||
reply.elements["vCard"] = vcard
|
reply.elements["vCard"] = vcard
|
||||||
@logger.debug reply.to_s
|
@logger.debug reply.to_s
|
||||||
@@transport.send(reply)
|
@component.send(reply)
|
||||||
# time response #
|
# time response #
|
||||||
elsif iq.type == :result and iq.elements["time"] and @sessions.key? iq.from.bare.to_s then
|
elsif iq.type == :result and iq.elements["time"] and @sessions.key? iq.from.bare.to_s then
|
||||||
@logger.debug "Timezone response from <%s>" % iq.from.to_s
|
@logger.debug "Timezone response from <%s>" % iq.from.to_s
|
||||||
|
@ -188,7 +211,7 @@ class XMPPComponent
|
||||||
reply = iq.answer
|
reply = iq.answer
|
||||||
reply.type = :error
|
reply.type = :error
|
||||||
end
|
end
|
||||||
@@transport.send(reply)
|
@component.send(reply)
|
||||||
end
|
end
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
|
@ -213,34 +236,24 @@ class XMPPComponent
|
||||||
@sessions[from.bare.to_s].disconnect(true) if @sessions.key? from.bare.to_s
|
@sessions[from.bare.to_s].disconnect(true) if @sessions.key? from.bare.to_s
|
||||||
self.update_db(from.bare.to_s, true)
|
self.update_db(from.bare.to_s, true)
|
||||||
@sessions.delete(from.bare.to_s)
|
@sessions.delete(from.bare.to_s)
|
||||||
when '/debug' # show some debug information
|
when '/info' # show some debug information
|
||||||
return if not @config[:admins].include? from.bare.to_s
|
return if not @config[:admins].include? from.bare.to_s
|
||||||
GC.start
|
response = "Information about this instance: \n\n"
|
||||||
dump = (defined? Memprof2) ? "/tmp/zhabogram.%s.dump" % Time.now.to_i : nil
|
|
||||||
Memprof2.report(out: dump) if dump
|
|
||||||
response = "Debug information: \n\n"
|
|
||||||
response += "Running from: %s\n" % `ps -p #{$$} -o lstart`.lines.last.strip
|
response += "Running from: %s\n" % `ps -p #{$$} -o lstart`.lines.last.strip
|
||||||
response += "Sessions: %d online | %d total \n" % [ @sessions.inject(0){ |cnt, (jid, sess)| cnt = (sess.online?) ? cnt + 1 : cnt }, @sessions.count]
|
|
||||||
response += "System memory used: %d KB\n" % `ps -o rss -p #{$$}`.lines.last.strip.to_i
|
response += "System memory used: %d KB\n" % `ps -o rss -p #{$$}`.lines.last.strip.to_i
|
||||||
response += "Objects memory allocated: %d bytes \n" % `cut -d' ' -f1 #{dump}`.lines.map(&:to_i).reduce(0, :+) if dump
|
response += "\n\nSessions: %d online | %d total \n" % [ @sessions.inject(0){ |cnt, (jid, sess)| cnt = (sess.online?) ? cnt + 1 : cnt }, @sessions.count]
|
||||||
response += "\nDetailed memory info saved to %s\n" % dump if dump
|
@sessions.each do |jid, session| response += "JID: %s | Login: %s | Status: %s (%s) | %s\n" % [jid, session.login, (session.online == true) ? 'Online' : 'Offline', session.auth_state, (session.me) ? session.format_username(session.me.id) : 'Unknown' ] end
|
||||||
response += "\nRun this transport with --profiler (depends on gem memprof2) to get detailed memory infnormation.\n" if not dump
|
|
||||||
self.message(from.bare, nil, response)
|
|
||||||
when '/sessions' # show active sessions
|
|
||||||
return if not @config[:admins].include? from.bare.to_s
|
|
||||||
response = "Active sessions list: \n\n"
|
|
||||||
@sessions.each do |jid, session| response += "JID: %s | Login: %s | Status: %s (%s) | Telegram profile: %s\n" % [jid, session.login, (session.online == true) ? 'Online' : 'Offline', session.auth_state, (session.me) ? session.format_username(session.me.id) : 'Unknown' ] end
|
|
||||||
self.message(from.bare, nil, response)
|
self.message(from.bare, nil, response)
|
||||||
when '/restart' # reset transport
|
when '/restart' # reset transport
|
||||||
return if not @config[:admins].include? from.bare.to_s
|
return if not @config[:admins].include? from.bare.to_s
|
||||||
self.message(from.bare, nil, 'Trying to restart all active sessions and reconnect to XMPP server..')
|
self.message(from.bare, nil, 'Trying to restart all active sessions and reconnect to XMPP server..')
|
||||||
sleep(0.5)
|
sleep(1)
|
||||||
Process.kill("INT", Process.pid)
|
Process.kill("INT", Process.pid)
|
||||||
else # unknown command -- display help #
|
else # unknown command -- display help #
|
||||||
self.message(from.bare, nil, ::HELP_MESSAGE)
|
self.message(from.bare, nil, ::HELP_MESSAGE)
|
||||||
end
|
end
|
||||||
|
|
||||||
return
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,19 +2,18 @@
|
||||||
require 'yaml'
|
require 'yaml'
|
||||||
require 'logger'
|
require 'logger'
|
||||||
require 'xmpp4r'
|
require 'xmpp4r'
|
||||||
|
require 'xmpp4r/discovery'
|
||||||
require 'digest'
|
require 'digest'
|
||||||
require 'base64'
|
require 'base64'
|
||||||
require 'sqlite3'
|
require 'sqlite3'
|
||||||
require 'tdlib-ruby'
|
require 'tdlib-ruby'
|
||||||
require 'memprof2' if ARGV.include? '--profiler'
|
|
||||||
require_relative 'inc/telegramclient'
|
require_relative 'inc/telegramclient'
|
||||||
|
require_relative 'inc/xmppregister'
|
||||||
|
require_relative 'inc/xmppgateway'
|
||||||
require_relative 'inc/xmppcomponent'
|
require_relative 'inc/xmppcomponent'
|
||||||
|
|
||||||
# profiler #
|
|
||||||
Memprof2.start if defined? Memprof2
|
|
||||||
|
|
||||||
# configuration
|
# configuration
|
||||||
Config = YAML.load_file(File.dirname(__FILE__) + '/config.yml')
|
Config = YAML.load_file(File.dirname(__FILE__) + '/config.yml')
|
||||||
TelegramClient.configure(Config['telegram']) # configure tdlib
|
TelegramClient.configure(Config['telegram']) # configure tdlib
|
||||||
Zhabogram = XMPPComponent.new(Config['xmpp']) # spawn zhabogram
|
Zhabogram = XMPPComponent.new(Config['xmpp']) # spawn zhabogram
|
||||||
loop do Zhabogram.connect(); sleep(1); end # forever loop jk till double ctrl+c
|
Zhabogram.connect()
|
||||||
|
|
Reference in a new issue