2019-04-06 06:12:54 +00:00
class TelegramClient
2019-05-04 20:17:29 +00:00
attr_reader :jid , :login , :online , :auth_state , :me
attr_accessor :timezone
2019-04-06 06:12:54 +00:00
# tdlib configuration, shared within all instances #
def self . configure ( params )
2019-04-07 11:58:07 +00:00
@@loglevel = params [ 'loglevel' ] || Logger :: DEBUG
@@content_path = params [ 'content_path' ] || '/tmp'
@@content_link = params [ 'content_link' ] || 'https://localhost/tg_media'
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
@@content_upload_prefix = params [ " content_upload_prefix " ] || 'https://localhost/upload/'
2019-04-06 06:12:54 +00:00
TD . configure do | config |
2019-04-07 11:58:07 +00:00
config . lib_path = params [ 'path' ] || 'lib/' # we hope it's here
2019-04-13 14:42:56 +00:00
config . client . api_id = params [ 'api_id' ] || '50322' # telegram app. from debian repositories
config . client . api_hash = params [ 'api_hash' ] || '9ff1a639196c0779c86dd661af8522ba' # telegram app. from debian repositories
config . client . device_model = params [ 'useragent' ] || 'Zhabogram'
config . client . application_version = params [ 'version' ] || '1.0' # hmm...
2019-04-07 11:58:07 +00:00
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?..
2019-05-31 11:30:25 +00:00
config . client . use_file_database = true # wow
2019-06-13 10:12:59 +00:00
config . client . use_message_database = true # such library
config . client . use_chat_info_database = true # much options
2019-05-07 19:22:26 +00:00
config . client . enable_storage_optimizer = false # ...
2019-04-06 06:12:54 +00:00
end
2019-04-07 11:58:07 +00:00
TD :: Api . set_log_verbosity_level ( params [ 'verbosity' ] || 1 )
2019-04-06 06:12:54 +00:00
end
# instance initialization #
2019-05-04 20:17:29 +00:00
def initialize ( xmpp , jid , login )
2019-05-04 22:42:59 +00:00
return if not @@loglevel # call .configure() first
2019-05-04 20:17:29 +00:00
@logger = Logger . new ( STDOUT ) ; @logger . level = @@loglevel ; @logger . progname = '[TelegramClient: %s/%s]' % [ jid , login ] # create logger
2019-05-04 22:42:59 +00:00
@logger . info 'Starting Telegram client..'
2019-05-04 20:17:29 +00:00
@xmpp = xmpp # XMPP stream
@jid = jid # user JID
@timezone = '-00:00' # default timezone is UTC
@login = login # telegram login
@me = nil # self telegram profile
@online = nil # we do not know
@auth_state = 'nil' # too.
2019-05-31 11:30:25 +00:00
@cache = { chats : { } , users : { } , photos : { } } # cache
2019-05-04 22:42:59 +00:00
end
# initialize and connect telegram client #
def connect ( )
return if @client and @client . ready?
@logger . info 'Connecting to Telegram network..'
2019-05-04 20:17:29 +00:00
@client = TD :: Client . new ( database_directory : 'sessions/' + @jid , files_directory : 'sessions/' + @jid + '/files/' ) # create telegram client instance
2019-05-31 22:35:03 +00:00
@client . on ( TD :: Types :: Update :: AuthorizationState ) do | update | self . auth_handler ( update ) end # register auth update handler
@client . on ( TD :: Types :: Update :: File ) do | update | self . file_handler ( update ) ; end # register file 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 :: NewChat ) do | update | self . new_chat_handler ( update ) end # register new chat handler
@client . on ( TD :: Types :: Update :: User ) do | update | self . user_handler ( update ) end # new user update?
@client . on ( TD :: Types :: Update :: UserStatus ) do | update | self . status_update_handler ( update ) end # register status handler
2019-05-04 22:42:59 +00:00
@client . connect ( )
2019-06-11 13:24:44 +00:00
return true
2019-05-04 20:17:29 +00:00
end
2019-05-04 22:42:59 +00:00
# disconnect and destroy telegram client #
2019-05-04 20:17:29 +00:00
def disconnect ( logout = false )
2019-05-04 22:42:59 +00:00
return if not @client
2019-05-04 20:17:29 +00:00
@logger . info 'Disconnecting..'
@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
2019-05-31 11:30:25 +00:00
@client = nil
2019-05-04 20:17:29 +00:00
@online = false
end
2019-04-06 07:29:48 +00:00
###########################################
## Callback handlers #####################
###########################################
2019-04-06 06:12:54 +00:00
# authorization handler #
def auth_handler ( update )
2019-04-06 07:29:48 +00:00
@logger . debug 'Authorization state changed: %s' % update . authorization_state
2019-05-04 20:17:29 +00:00
@auth_state = update . authorization_state . class . name
2019-04-06 06:12:54 +00:00
2019-04-06 07:29:48 +00:00
case update . authorization_state
2019-04-06 06:12:54 +00:00
# auth stage 0: specify login #
when TD :: Types :: AuthorizationState :: WaitPhoneNumber
2019-04-06 07:29:48 +00:00
@logger . info 'Logging in..'
2019-04-06 06:12:54 +00:00
@client . set_authentication_phone_number ( @login )
# auth stage 1: wait for authorization code #
when TD :: Types :: AuthorizationState :: WaitCode
2019-04-06 07:29:48 +00:00
@logger . info 'Waiting for authorization code..'
2019-05-04 20:17:29 +00:00
@xmpp . message ( @jid , nil , 'Please, enter authorization code via /code 12345' )
2019-04-06 06:12:54 +00:00
# auth stage 2: wait for 2fa passphrase #
when TD :: Types :: AuthorizationState :: WaitPassword
2019-04-06 07:29:48 +00:00
@logger . info 'Waiting for 2FA password..'
2019-05-04 20:17:29 +00:00
@xmpp . message ( @jid , nil , 'Please, enter 2FA passphrase via /password 12345' )
2019-04-07 11:58:07 +00:00
# authorization successful -- indicate that client is online and retrieve contact list #
2019-04-06 06:12:54 +00:00
when TD :: Types :: AuthorizationState :: Ready
2019-04-06 07:29:48 +00:00
@logger . info 'Authorization successful!'
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
@client . get_me ( ) . then { | user | @me = user } . wait
2019-04-13 14:42:56 +00:00
@client . get_chats ( limit = 9999 )
2019-04-07 11:58:07 +00:00
@logger . info " Contact list updating finished "
2019-05-04 20:17:29 +00:00
@xmpp . presence ( @jid , nil , :subscribe )
2019-05-07 19:22:26 +00:00
@xmpp . presence ( @jid , nil , nil , nil , " Logged in as %s " % @login )
@online = true
2019-04-13 14:42:56 +00:00
# closing session: sent offline presences to XMPP user #
when TD :: Types :: AuthorizationState :: Closing
@logger . info 'Closing session..'
self . disconnect ( )
# session closed gracefully
2019-04-07 11:58:07 +00:00
when TD :: Types :: AuthorizationState :: Closed
@logger . info 'Session closed.'
2019-04-13 14:42:56 +00:00
self . disconnect ( )
2019-04-06 06:12:54 +00:00
end
end
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
2019-04-06 06:12:54 +00:00
# message from telegram network handler #
2019-04-14 14:41:40 +00:00
def message_handler ( update , show_date = false )
2019-05-31 11:30:25 +00:00
return if update . message . is_outgoing and update . message . sending_state . instance_of? TD :: Types :: MessageSendingState :: Pending # ignore self outgoing messages
2019-04-09 06:42:42 +00:00
@logger . debug 'Got NewMessage update'
2019-04-07 11:58:07 +00:00
@logger . debug update . message . to_json
2019-04-16 03:45:56 +00:00
@logger . info 'New message from Telegram chat %s' % update . message . chat_id
2019-05-31 11:30:25 +00:00
# message content
prefix = [ ]
content = update . message . content
# file handling
file = case content
when TD :: Types :: MessageContent :: Sticker then [ content . sticker . sticker , content . sticker . emoji + '.webp' ]
2019-05-31 22:35:03 +00:00
when TD :: Types :: MessageContent :: VoiceNote then [ content . voice_note . voice , 'voice message (%i seconds).oga' % content . voice_note . duration ]
when TD :: Types :: MessageContent :: VideoNote then [ content . video_note . video , 'video message (%i seconds).mp4' % content . video_note . duration ]
2019-05-31 11:30:25 +00:00
when TD :: Types :: MessageContent :: Animation then [ content . animation . animation , content . animation . file_name + '.mp4' ]
when TD :: Types :: MessageContent :: Photo then [ content . photo . sizes [ - 1 ] . photo , content . photo . id + '.jpg' ]
when TD :: Types :: MessageContent :: Audio then [ content . audio . audio , content . audio . file_name ]
when TD :: Types :: MessageContent :: Video then [ content . video . video , content . video . file_name ]
when TD :: Types :: MessageContent :: Document then [ content . document . document , content . document . file_name ]
end
2019-04-16 03:45:56 +00:00
2019-05-31 11:30:25 +00:00
# text handling
text = case content
when TD :: Types :: MessageContent :: BasicGroupChatCreate , TD :: Types :: MessageContent :: SupergroupChatCreate then " has created chat "
when TD :: Types :: MessageContent :: ChatJoinByLink then " joined chat via invite link "
2019-05-31 22:35:03 +00:00
when TD :: Types :: MessageContent :: ChatAddMembers then " invited %s " % self . format_contact ( message . content . member_user_ids . first )
when TD :: Types :: MessageContent :: ChatDeleteMember then " kicked %s " % self . format_contact ( update . message . content . user_id )
2019-05-31 11:30:25 +00:00
when TD :: Types :: MessageContent :: PinMessage then " pinned message: %s " % self . format_message ( update . message . chat_id , content . message_id )
when TD :: Types :: MessageContent :: ChatChangeTitle then " chat title set to: %s " % update . message . content . title . to_s
when TD :: Types :: MessageContent :: Location then " coordinates: %s | https://www.google.com/maps/search/%s,%s/ " % [ content . location . latitude , content . location . longitude ]
when TD :: Types :: MessageContent :: Photo , TD :: Types :: MessageContent :: Audio , TD :: Types :: MessageContent :: Video , TD :: Types :: MessageContent :: Document then content . caption . text
when TD :: Types :: MessageContent :: Text then content . text . text
2019-05-31 22:35:03 +00:00
when TD :: Types :: MessageContent :: VoiceNote then content . caption . text
when TD :: Types :: MessageContent :: VideoNote then ''
2019-05-31 11:30:25 +00:00
else " unknown message type %s " % update . message . content . class
end
# download file if needed
@client . download_file ( file [ 0 ] . id ) if file and not file [ 0 ] . local . is_downloading_completed
2019-05-07 19:22:26 +00:00
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# forwards, replies and message id..
2019-05-31 11:30:25 +00:00
prefix << DateTime . strptime ( ( update . message . date + Time . now . getlocal ( @timezone ) . utc_offset ) . to_s , '%s' ) . strftime ( " %d %b %Y %H:%M:%S " ) if show_date # show date if its
prefix << ( update . message . is_outgoing ? '➡ ' : '⬅ ' ) + update . message . id . to_s # message direction
2019-05-31 22:35:03 +00:00
prefix << " %s " % self . format_contact ( update . message . sender_user_id ) if update . message . chat_id < 0 # show sender in group chats
prefix << " fwd: %s " % self . format_contact ( update . message . forward_info . sender_user_id ) if update . message . forward_info . instance_of? TD :: Types :: MessageForwardInfo :: MessageForwardedFromUser # fwd from user
prefix << " fwd: %s%s " % [ self . format_contact ( update . message . forward_info . chat_id ) , ( update . message . forward_info . author_signature != '' ) ? " (%s) " % update . message . forward_info . author_signature : '' ] if update . message . forward_info . instance_of? TD :: Types :: MessageForwardInfo :: MessageForwardedPost # fwd from chat
2019-05-31 11:30:25 +00:00
prefix << " reply: %s " % self . format_message ( update . message . chat_id , update . message . reply_to_message_id , false ) if update . message . reply_to_message_id . to_i != 0 # reply to
prefix << " file: %s " % self . format_file ( file [ 0 ] , file [ 1 ] ) if file
2019-05-31 22:35:03 +00:00
prefix = prefix . join ( ' | ' )
prefix += ( update . message . chat_id < 0 and text and text != " " ) ? " \n " : '' # \n if it is groupchat and message is not empty
prefix += ( update . message . chat_id > 0 and text and text != " " ) ? " | " : ''
2019-05-31 11:30:25 +00:00
# read message & send it to xmpp
@client . view_messages ( update . message . chat_id , [ update . message . id ] , force_read : true )
2019-05-31 22:35:03 +00:00
@xmpp . message ( @jid , update . message . chat_id . to_s , prefix + text )
2019-04-07 11:58:07 +00:00
end
# new chat update -- when tg client discovers new chat #
def new_chat_handler ( update )
2019-04-09 06:42:42 +00:00
@logger . debug 'Got NewChat update'
2019-04-07 11:58:07 +00:00
@logger . debug update . to_json
self . process_chat_info ( update . chat . id )
end
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# user -- something changed in user data #
def user_handler ( update )
@logger . debug 'Got User update'
@logger . debug update . to_json
self . process_user_info ( update . user . id )
end
2019-04-07 11:58:07 +00:00
# edited msg #
def message_edited_handler ( update )
2019-04-09 06:42:42 +00:00
@logger . debug 'Got MessageEdited update'
2019-04-07 11:58:07 +00:00
@logger . debug update . to_json
# formatting
2019-04-16 03:45:56 +00:00
text = " ✎ %s | %s " % [ update . message_id . to_s , update . new_content . text . text . to_s ]
2019-05-04 20:17:29 +00:00
@xmpp . message ( @jid , update . chat_id . to_s , text )
2019-04-06 06:12:54 +00:00
end
2019-04-07 11:58:07 +00:00
# deleted msg #
def message_deleted_handler ( update )
2019-04-09 06:42:42 +00:00
@logger . debug 'Got MessageDeleted update'
2019-04-07 11:58:07 +00:00
@logger . debug update . to_json
return if not update . is_permanent
2019-04-16 03:45:56 +00:00
text = " ✗ %s | " % update . message_ids . join ( ',' )
2019-05-04 20:17:29 +00:00
@xmpp . message ( @jid , update . chat_id . to_s , text )
2019-04-07 11:58:07 +00:00
end
# status update handler #
def status_update_handler ( update )
2019-04-09 06:42:42 +00:00
@logger . debug 'Got new StatusUpdate'
2019-04-07 11:58:07 +00:00
@logger . debug update . to_json
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
return if update . user_id == @me . id # ignore self statuses
2019-05-07 19:22:26 +00:00
self . process_status_update ( update . user_id , update . status , false )
2019-04-07 11:58:07 +00:00
end
2019-05-31 11:30:25 +00:00
# file msg -- symlink to download path #
def file_handler ( update )
@logger . debug 'Got File update'
@logger . debug update . to_json
if update . file . local . is_downloading_completed then
source = update . file . local . path . to_s
target = self . format_file ( update . file , update . file . local . path , true )
@logger . debug 'Downloading of <%s> completed! Created link to <%s>' % [ source , target ]
File . symlink ( source , target )
end
end
2019-04-06 06:12:54 +00:00
2019-04-06 07:29:48 +00:00
###########################################
## LooP handlers #########################
###########################################
2019-04-06 06:12:54 +00:00
# processing authorization #
2019-04-13 14:42:56 +00:00
def process_auth ( typ , auth_data )
2019-04-16 03:45:56 +00:00
@logger . info " Authorizing with :%s.. " % typ
2019-04-13 14:42:56 +00:00
@client . check_authentication_code ( auth_data ) if typ == '/code'
@client . check_authentication_password ( auth_data ) if typ == '/password'
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
end
# /command #
def process_command ( chat_id , text )
2019-05-31 22:35:03 +00:00
arg = text [ 0 .. 2 ] == '/s/' ? [ '/sed' , text [ 3 .. - 1 ] ] : text . split
# ..
if arg [ 1 ] and arg [ 1 ] [ 0 ] == '@' then @client . search_public_chat ( arg [ 1 ] [ 1 .. - 1 ] ) . then { | c | resolve = c } . wait end # try to resolve @username from second arg #
if arg [ 1 ] . to_i < 0 then resolve = self . process_chat_info ( arg [ 1 ] . to_i , false ) end # try to resolve chat_id/user_id from second arg
if arg [ 1 ] . to_i > 0 then resolve = self . process_user_info ( arg [ 1 ] . to_i ) end # try to resolve user_id from second arg
# command...
response = nil
current = @cache [ :chats ] [ chat_id ] # current chat
resolve = resolve || nil # resolved chat or nil
chat = resolve || current # resolved chat or current
case arg [ 0 ]
when '/info' then response = self . format_contact ( chat . id ) # print information
when '/add' then ( chat . id > 0 ) ? self . process_chat_info ( chat . id , true ) : @client . join_chat ( chat . id ) . wait # add contact
when '/join' then @client . join_chat_by_invite_link ( arg [ 1 ] ) . wait if arg [ 1 ] [ 0 .. 3 ] == 'http' # join chat by link
when '/secret' then @client . create_new_secret_chat ( chat . id ) . wait if chat . id > 0 # new secret chat
when '/group' then @client . create_new_basic_group_chat ( resolve . id , arg [ 2 ] ) . it if resolve and arg [ 2 ]
when '/supergroup' then @client . create_new_supergroup_chat ( arg [ 1 ] , arg [ 2 ] ) . wait if arg [ 2 ]
when '/channel' then @client . create_new_supergroup_chat ( arg [ 1 ] , arg [ 2 ] , is_channel : true ) . wait if arg [ 2 ]
when '/invite' then @client . add_chat_member ( current . id , resolve . id ) . wait if resolve
when '/kick' then @client . set_chat_member_status ( current , resolve . id , TD :: Types :: ChatMemberStatus :: Left . new ( ) ) . wait if resolve
when '/ban' then @client . set_chat_member_status ( current . id , resolve . id , TD :: Types :: ChatMemberStatus :: Banned . new ( banned_until_date : ( arg [ 1 ] ) ? Time . now . getutc . to_i + arg [ 1 ] . to_i * 3600 : 0 ) ) . wait if resolve
when '/block' then @client . block_user ( current . id ) . wait
when '/unblock' then @client . unblock_user ( current . id ) . wait
when '/members' then members = [ ]
response = " - Members of chat %s - \n \n " % current . title
@client . search_chat_members ( current . id , filter : TD :: Types :: ChatMembersFilter :: Members . new ) . then { | m | members += m . members } . wait if current . type . instance_of? TD :: Types :: ChatType :: BasicGroup # basic
@client . get_supergroup_members ( current . type . supergroup_id ) . then { | m | members += m . members } . wait if current . type . instance_of? TD :: Types :: ChatType :: Supergroup # super
members . each do | user | response += " %s | Role: %s \n " % [ self . format_contact ( user . user_id , true , false ) , user . status . class ] end
when '/leave' , '/delete' then @client . close_chat ( current . id ) . wait
@client . leave_chat ( current . id ) if current . type . instance_of? TD :: Types :: ChatType :: BasicGroup or current . type . instance_of? TD :: Types :: ChatType :: Supergroup
@client . close_secret_chat ( current . type . secret_chat_id ) . wait if current . type . instance_of? TD :: Types :: ChatType :: Secret
@client . delete_chat_history ( current . id , true ) . wait
@xmpp . presence ( @jid , current . id . to_s , :unsubscribed )
@xmpp . presence ( @jid , current . id . to_s , :unavailable )
@cache [ :chats ] . delete ( current . id ) if @cache [ :chats ] . key? current . id
@cache [ :users ] . delete ( current . id ) if @cache [ :users ] . key? current . id
when '/sed' then id , edited = nil , nil
sed = arg [ 1 ] . split ( '/' )
@client . search_chat_messages ( current . id , 0 , 1 , sender_user_id : @me . id , filter : TD :: Types :: SearchMessagesFilter :: Empty . new ) . then { | m | id , edited = m . messages [ 0 ] . id , m . messages [ 0 ] . content . text . text . to_s } . wait
@client . edit_message_text ( current . id , id , TD :: Types :: InputMessageContent :: Text . new ( text : { text : edited . gsub ( Regexp . new ( sed [ 0 ] ) , sed [ 1 ] ) , entities : [ ] } , disable_web_page_preview : false , clear_draft : true ) ) . wait if id
when '/d' then id = arg [ 1 ] . to_i
@client . search_chat_messages ( current . id , 0 , 1 , sender_user_id : @me . id , filter : TD :: Types :: SearchMessagesFilter :: Empty . new ) . then { | m | id = m . messages [ 0 ] . id } . wait if id == 0
@client . delete_messages ( current . id , [ id ] , true )
when '/search' then count = arg [ 1 ] || 10
query = arg [ 2 ] || nil
@client . search_chat_messages ( current . id , 0 , count , query : query , filter : TD :: Types :: SearchMessagesFilter :: Empty . new ) . then { | msgs |
msgs . messages . reverse . each do | msg | self . message_handler ( TD :: Types :: Update :: NewMessage . new ( message : msg , disable_notification : false , contains_mention : false ) , true ) end
} . wait
when '/setusername' then @client . set_username ( arg [ 1 ] || '' )
when '/setname' then @client . set_name ( arg [ 1 ] || '' , arg [ 2 ] || '' )
when '/setbio' then @client . set_bio ( arg [ 1 .. 99 ] . join ( ' ' ) )
when '/setpassword' then old_password , new_password = arg [ 1 ] , arg [ 2 ]
old_password = '' if old_password == 'nil'
new_password = nil if new_password == 'nil'
@client . set_password ( old_password , new_password : new_password )
when '/dump' then response = current . to_json
else response = ' Unknown command .
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
2019-05-31 22:35:03 +00:00
/ s /mi tsake / mistake / — Edit last message
/ d — Delete last message
/ info id — Information about user / chat by its id
/ add @username or id — Create conversation with specified user or chat id
/ join chat_link or id — Join chat by its link or id
/ secret @username — Create "secret chat" with specified user
/ group @username groupname — Create group chat named groupname with @username
/ supergroup name description — Create supergroup chat
/ channel name description — Create channel
/ members — Supergroup members
/ search count query — Search in chat history
/ invite @username — Invite @username to current chat
/ kick @username — Remove @username from current chat
/ ban @username [hours] — Ban @username in current chat for [hours] hrs or forever if [hours] not specified
/ block — Blacklistscurrent user
/ unblock — Remove current user from blacklist
/ delete — Delete current chat
/ leave — Leave current chat
/ setusername username — Set username
/ setname First Last — Set name
/ setbio Bio — Set bio
/ setpassword old new — Set 2FA password (use "nil" for no password")
'
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
end
2019-05-07 19:22:26 +00:00
@xmpp . message ( @jid , chat_id . to_s , response ) if response
2019-04-06 06:12:54 +00:00
end
# processing outgoing message from queue #
2019-04-13 14:42:56 +00:00
def process_outgoing_msg ( chat_id , text )
2019-04-16 03:45:56 +00:00
@logger . info 'Sending message to Telegram chat %s...' % chat_id
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# processing /commands #
2019-04-16 14:07:15 +00:00
return if not @cache [ :chats ] . key? chat_id # null chat
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
return self . process_command ( chat_id , text ) if text [ 0 ] == '/'
2019-04-08 22:28:26 +00:00
# handling replies #
2019-05-31 22:35:03 +00:00
reply_to = 0
2019-06-11 13:24:44 +00:00
if text [ 0 ] == '>' and text . match ( Regexp . new / ^>( )?[0-9]{10,20} / ) then
2019-05-31 22:35:03 +00:00
text = text . split ( " \n " )
2019-06-11 13:24:44 +00:00
reply_to = text [ 0 ] . scan ( / \ d+ / ) . first . to_i
text = text . drop ( 1 ) . join ( " \n " )
2019-04-08 22:28:26 +00:00
end
2019-06-11 13:24:44 +00:00
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# handling files received from xmpp #
2019-05-31 22:35:03 +00:00
message = TD :: Types :: InputMessageContent :: Text . new ( :text = > { :text = > text , :entities = > [ ] } , :disable_web_page_preview = > false , :clear_draft = > true )
message = TD :: Types :: InputMessageContent :: Document . new ( document : TD :: Types :: InputFile :: Remote . new ( id : text ) , caption : { :text = > '' , :entities = > [ ] } ) if text . start_with? @@content_upload_prefix
2019-04-08 22:28:26 +00:00
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# send message and mark chat as read #
2019-04-08 22:28:26 +00:00
@client . send_message ( chat_id , message , reply_to_message_id : reply_to )
2019-04-06 06:12:54 +00:00
end
2019-04-07 11:58:07 +00:00
# update users information and save it to cache #
2019-04-15 05:03:40 +00:00
def process_chat_info ( chat_id , subscription = true )
2019-04-09 06:42:42 +00:00
@logger . debug 'Updating chat id %s..' % chat_id . to_s
2019-05-31 11:30:25 +00:00
@client . get_chat ( chat_id ) . then { | chat |
2019-04-07 11:58:07 +00:00
@cache [ :chats ] [ chat_id ] = chat # cache chat
2019-05-31 11:30:25 +00:00
@client . download_file ( chat . photo . small . id ) . then { | f | @cache [ :photos ] [ chat_id ] = f } . wait if chat . photo # download userpic
@xmpp . presence ( @jid , chat_id . to_s , :subscribe , nil , nil , chat . title . to_s ) if subscription # send subscription request
self . process_status_update ( chat_id , chat . title . to_s , true ) if chat . id < 0 # groups presence
2019-04-07 11:58:07 +00:00
} . wait
2019-05-31 22:35:03 +00:00
return @cache [ :chats ] [ chat_id ] if @cache [ :chats ] . key? chat_id
2019-04-07 11:58:07 +00:00
end
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
# update user info in cache and sync status to roster if needed #
2019-04-07 11:58:07 +00:00
def process_user_info ( user_id )
2019-05-31 11:30:25 +00:00
@logger . debug 'Updating user id %s..' % user_id
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
@client . get_user ( user_id ) . then { | user |
@cache [ :users ] [ user_id ] = user # add to cache
2019-05-31 11:30:25 +00:00
@client . get_user_full_info ( user_id ) . then { | bio | @cache [ :chats ] [ user_id ] . attributes [ :client_data ] = bio . bio } . wait
self . process_status_update ( user_id , user . status , true ) # status update
2019-04-13 14:42:56 +00:00
} . wait
2019-05-31 22:35:03 +00:00
return @cache [ :users ] [ user_id ] if @cache [ :users ] . key? user_id
2019-04-07 11:58:07 +00:00
end
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
2019-06-11 13:24:44 +00:00
# sync statuses with XMPP roster
def sync_status ( )
@logger . debug " Syncing statuses with roster.. "
2019-06-13 10:12:59 +00:00
@cache [ :chats ] . each_value do | chat | self . process_status_update ( chat . id , ( chat . id > 0 and @cache [ :users ] . include? chat . id ) ? @cache [ :users ] [ chat . id ] . status : chat . title . to_s , true ) end
2019-06-11 13:24:44 +00:00
end
2019-04-07 11:58:07 +00:00
# convert telegram status to XMPP one
2019-05-07 19:22:26 +00:00
def process_status_update ( user_id , status , immed = true )
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
@logger . debug " Processing status update for user id %s.. " % user_id . to_s
2019-05-31 11:30:25 +00:00
xmpp_show , xmpp_status , xmpp_photo = nil
2019-04-07 11:58:07 +00:00
case status
2019-05-31 22:35:03 +00:00
when TD :: Types :: UserStatus :: Online then xmpp_show , xmpp_status = nil , " Online "
when TD :: Types :: UserStatus :: Offline then xmpp_show , xmpp_status = ( Time . now . getutc . to_i - status . was_online . to_i < 3600 ) ? :away : :xa , DateTime . strptime ( ( status . was_online + Time . now . getlocal ( @timezone ) . utc_offset ) . to_s , '%s' ) . strftime ( " Last seen at %H:%M %d/%m/%Y " )
when TD :: Types :: UserStatus :: Recently then xmpp_show , xmpp_status = :dnd , " Last seen recently "
when TD :: Types :: UserStatus :: LastWeek then xmpp_show , xmpp_status = :unavailable , " Last seen last week "
when TD :: Types :: UserStatus :: LastMonth then xmpp_show , xmpp_status = :unavailable , " Last seen last month "
else xmpp_show , xmpp_status = :chat , status
2019-04-07 11:58:07 +00:00
end
2019-05-31 11:30:25 +00:00
xmpp_photo = self . format_file ( @cache [ :photos ] [ user_id ] , 'image.jpg' , true ) if @cache [ :photos ] . include? user_id
xmpp_photo = ( File . exist? xmpp_photo . to_s ) ? Base64 . encode64 ( IO . binread ( xmpp_photo ) ) : nil
2019-05-31 22:35:03 +00:00
# ...
return @xmpp . presence ( @jid , user_id . to_s , nil , xmpp_show , xmpp_status , nil , xmpp_photo , immed )
2019-04-13 14:42:56 +00:00
end
# get contact information (for vcard).
def get_contact_info ( chat_id )
return if not @cache [ :chats ] . key? chat_id # no such chat #
username , firstname , lastname , phone , bio , userpic = nil
title = @cache [ :chats ] [ chat_id ] . title # <FN>
# user information
if @cache [ :users ] . key? chat_id then # its an user
firstname = @cache [ :users ] [ chat_id ] . first_name # <N/GIVEN>
lastname = @cache [ :users ] [ chat_id ] . last_name # <N/FAMILY>
username = @cache [ :users ] [ chat_id ] . username # <NICKNAME>
phone = @cache [ :users ] [ chat_id ] . phone_number # <TEL>
2019-05-31 11:30:25 +00:00
bio = @cache [ :chats ] [ chat_id ] . client_data # <DESC>
userpic = self . format_file ( @cache [ :photos ] [ chat_id ] , 'image.jpg' , true ) if @cache [ :photos ] . include? chat_id
userpic = ( File . exist? userpic . to_s ) ? Base64 . encode64 ( IO . binread ( userpic ) ) : nil
2019-04-13 14:42:56 +00:00
end
# ..
2019-04-14 14:41:40 +00:00
return title , username , firstname , lastname , phone , bio , userpic
2019-04-13 14:42:56 +00:00
end
2019-05-07 19:22:26 +00:00
# resolve id by @username (or just return id)
def resolve_username ( username )
2019-05-31 22:35:03 +00:00
resolved = username
if username [ 0 ] == '@' then @client . search_public_chat ( username [ 1 .. - 1 ] ) . then { | chat | resolved = '@' + chat . id . to_s } . wait end
if username [ 0 .. 3 ] == 'http' or username [ 0 .. 3 ] == 't.me' then @client . join_chat_by_invite_link ( username ) end
return resolved
2019-05-07 19:22:26 +00:00
end
Release v0.3
[UPD] more correct way to handle user/chat/auth updates
[UPD] removed successful login message, using presence instead
[NEW] config option `content_upload_prefix` that says which url prefixes will be treated as media and uploaded as file to telegram
[NEW] processing self outgoing messages that sent via another telegram instance
[NEW] added gif animations, locations, video, chat events support
[NEW] control commands implemented:
/s/old/new (edit), /d (delete), /info @username, /add @username or uid, /join invite.link or id, /invite @username, /kick @username, /ban @username [hours], /block, /unblock, /leave, /delete
2019-04-12 03:11:56 +00:00
###########################################
## Format functions #######################
###########################################
2019-04-07 11:58:07 +00:00
# format tg user name #
2019-05-31 22:35:03 +00:00
def format_contact ( id , show_id = false , resolve = true )
fmt = ''
if id < 0 then # its chat
fmt = ( @cache [ :chats ] . key? id ) ? " %s " % @cache [ :chats ] [ id ] . title : " %s " % id
elsif id > 0 then # its user
self . process_user_info ( id ) if not @cache [ :users ] . key? id and resolve
user = @cache [ :users ] [ id ] if @cache [ :users ] . key? id
fmt += user . first_name if user and user . first_name != ''
fmt += " " + user . last_name if user and user . last_name != ''
fmt += " (@%s) " % user . username if user and user . username != ''
fmt += " (%s) " % id if ( user and user . username == '' ) or show_id
else
fmt = " unknown (%s) " % id
end
2019-04-14 02:56:29 +00:00
2019-05-31 22:35:03 +00:00
return fmt
2019-04-07 11:58:07 +00:00
end
2019-05-31 22:35:03 +00:00
2019-04-14 02:56:29 +00:00
# format reply#
2019-05-31 11:30:25 +00:00
def format_message ( chat_id , message_id , full = true )
2019-04-14 02:56:29 +00:00
text = ''
2019-05-31 11:30:25 +00:00
@client . get_message ( chat_id , message_id ) . then { | message | text = message . content . text . text } . wait
2019-05-31 22:35:03 +00:00
return ( not full ) ? " %s >> %s.. " % [ message_id , text . split ( " \n " ) [ 0 ] ] : " %s | %s " % [ message_id , text ]
2019-04-14 02:56:29 +00:00
end
2019-05-31 11:30:25 +00:00
def format_file ( file , filename , local = false )
if local then return " %s/%s%s " % [ @@content_path , Digest :: SHA256 . hexdigest ( file . remote . id ) , File . extname ( filename ) ] end
return " %s (%d kbytes) | %s/%s%s " % [ filename , file . size / 1024 , @@content_link , Digest :: SHA256 . hexdigest ( file . remote . id ) , File . extname ( filename ) . to_s ]
2019-04-07 11:58:07 +00:00
end
2019-05-31 11:30:25 +00:00
2019-05-04 21:21:59 +00:00
# state functions #
2019-05-04 20:17:29 +00:00
def online? ( ) @online end
def tz_set? ( ) return @timezone != '-00:00' end
2019-04-06 06:12:54 +00:00
end