From d20414617ae39feed07718ebdb4bd1db6a2dd32e Mon Sep 17 00:00:00 2001 From: annelin Date: Sun, 14 Apr 2019 17:41:40 +0300 Subject: [PATCH] Release 0.6 [NEW] broadcasting vcard photo update [NEW] new commands: /secret, /group, /supergroup, /channel, /search, /history (send /help to any contact) [UPD] improved formatting --- inc/telegramclient.rb | 90 +++++++++++++++++++++++++++++-------------- inc/xmppcomponent.rb | 19 +++++++-- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/inc/telegramclient.rb b/inc/telegramclient.rb index 6377389..cba9124 100644 --- a/inc/telegramclient.rb +++ b/inc/telegramclient.rb @@ -33,7 +33,7 @@ class TelegramClient @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. @login = login # store tg login - @cache = {chats: {}, users: {}, users_fi: {}, unread_msg: {} } # we will store our cache here + @cache = {chats: {}, users: {}, users_fi: {}, userpics: {}, unread_msg: {} } # we will store our cache here @files_dir = File.dirname(__FILE__) + '/../sessions/' + @xmpp.user_jid + '/files/' # spawn telegram client and specify callback handlers @@ -91,7 +91,7 @@ class TelegramClient end # message from telegram network handler # - def message_handler(update) + def message_handler(update, show_date = false) @logger.debug 'Got NewMessage update' @logger.debug update.message.to_json @@ -100,6 +100,7 @@ class TelegramClient # media? # file = nil + prefix = '' text = '' case update.message.content when TD::Types::MessageContent::Photo # photos @@ -145,10 +146,14 @@ class TelegramClient @client.download_file(file.id) if file # download it if already not # forwards, replies and message id.. - text = "| fwd %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 from user - text = "| fwd %s(%s) | %s" % [self.format_chatname(update.message.forward_info.chat_id), update.message.forward_info.author_signature.to_s, text] if update.message.forward_info.instance_of? TD::Types::MessageForwardInfo::MessageForwardedPost # fwd from chat - text = "| reply %s | %s" % [self.format_reply(update.message.chat_id, update.message.reply_to_message_id), text] if update.message.reply_to_message_id.to_i != 0 # reply - text = "%s | %s | %s" % [update.message.id.to_s, self.format_username(update.message.sender_user_id), text] # username/id + prefix += "[%s]" % DateTime.strptime((update.message.date+Time.now.getlocal(@xmpp.timezone).utc_offset).to_s,'%s').strftime("[%d %b %Y %H:%M:%S]") if show_date + prefix += "fwd from %s | " % self.format_username(update.message.forward_info.sender_user_id) if update.message.forward_info.instance_of? TD::Types::MessageForwardInfo::MessageForwardedFromUser # fwd from user + prefix += "fwd from %s | " % self.format_chatname(update.message.forward_info.chat_id) if update.message.forward_info.instance_of? TD::Types::MessageForwardInfo::MessageForwardedPost # fwd from chat + prefix += "reply to %s | " % self.format_reply(update.message.chat_id, update.message.reply_to_message_id) if update.message.reply_to_message_id.to_i != 0 # reply to + + # text formatting + text = "%s | %s | %s\n%s" % [update.message.id, self.format_username(update.message.sender_user_id), prefix, text] if update.message.chat_id < 0 # groupchats + text = "%s %s | %s%s" % [(update.message.is_outgoing ? '→' : '←'), update.message.id.to_s, prefix, text] if update.message.chat_id > 0 # private chats # send and add message id to unreads @cache[:unread_msg][update.message.chat_id] = update.message.id @@ -175,7 +180,7 @@ class TelegramClient @logger.debug update.to_json # formatting - text = "[MSG %s EDIT] %s" % [update.message_id.to_s, update.new_content.text.text.to_s] + text = "| %s edit | %s" % [update.message_id.to_s, update.new_content.text.text.to_s] @xmpp.incoming_message(update.chat_id.to_s, text) end @@ -184,7 +189,7 @@ class TelegramClient @logger.debug 'Got MessageDeleted update' @logger.debug update.to_json return if not update.is_permanent - text = "[MSG %s DELETE]" % update.message_ids.join(',') + text = "| %s del |" % update.message_ids.join(',') @xmpp.incoming_message(update.chat_id.to_s, text) end @@ -236,6 +241,14 @@ class TelegramClient when '/join' # join group/supergroup by invite link or by id chat = (resolved) ? resolved.id : splitted[1] 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 + @client.create_new_secret_chat(resolved.id) if resolved + when '/group' # create new group with @user_id + @client.create_new_basic_group_chat([resolved.id], splitted[2]) if resolved and splitted[2] + when '/supergroup' # create new supergroup + @client.create_new_supergroup_chat(splitted[1], splitted[2]) if splitted[2] + when '/channel' # create new channel + @client.create_new_supergroup_chat(splitted[1], splitted[2], is_channel: true) if splitted[2] when '/invite' # invite user to chat @client.add_chat_member(chat_id, resolved.id).wait if resolved when '/kick' # removes user from chat @@ -250,6 +263,7 @@ class TelegramClient when '/leave', '/delete' # delete / leave chat @client.close_chat(chat_id).wait @client.leave_chat(chat_id).wait + @client.close_secret_chat(chat_id).wait @client.delete_chat_history(chat_id, true).wait @xmpp.presence(chat_id, :unsubscribed) @xmpp.presence(chat_id, :unavailable) @@ -262,24 +276,41 @@ class TelegramClient @client.edit_message_text(chat_id, msgs.messages[0].id, TD::Types::InputMessageContent::Text.new(:text => { :text => edited, :entities => []}, :disable_web_page_preview => false, :clear_draft => true )) if edited != original }.wait when '/d' # delete last message - @client.search_chat_messages(chat_id, 0, 1, sender_user_id: @me.id, filter: TD::Types::SearchMessagesFilter::Empty.new).then {|msgs| - @client.delete_messages(chat_id, [msgs.messages[0].id], true) - }.wait + id = splitted[1].to_i + @client.search_chat_messages(chat_id, 0, 1, sender_user_id: @me.id, filter: TD::Types::SearchMessagesFilter::Empty.new).then {|msgs| id = msgs.messages[0].id }.wait if not id or id == 0 + @client.delete_messages(chat_id, [id], true) when '/dump' response = @cache[:chats][chat_id].to_json + when '/search' + count = (splitted[1]) ? splitted[1].to_i : 10 + query = (splitted[2]) ? splitted[2] : nil + @client.search_chat_messages(chat_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 '/history' + count = (splitted[1]) ? splitted[1].to_i : 10 + @client.get_chat_history(chat_id, 0, 0, count).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 else response = 'Unknown command. /s/mitsake/mistake/ — Edit last message /d — Delete last message - /add @username or id — Creates conversation with specified user - /join chat_link or id — Joins chat by its link or id - /invite @username — Invites @username to current chat - /kick @username — Removes @username from current chat - /ban @username [hours] — Bans @username in current chat for [hours] hrs or forever if [hours] not specified - /block — Blacklists current user - /unblock — Removes current user from blacklist + /add @username or id — Create conversation with specified user or chat 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 + /history count — Retrieve chat history + /search count query — Search in chat history + /join chat_link or id — Join chat by its link or id + /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 /leave — Leave current chat /delete — Delete current chat ' @@ -323,9 +354,10 @@ class TelegramClient # fullfil cache.. pasha durov, privet. # @client.get_chat(chat_id).then { |chat| @cache[:chats][chat_id] = chat # cache chat - @client.download_file(chat.photo.small.id) if chat.photo # download userpic + @client.download_file(chat.photo.small.id).wait if chat.photo # download userpic + @cache[:userpics][chat_id] = Digest::SHA1.hexdigest(IO.binread(self.format_content_link(chat.photo.small.remote.id, 'image.jpg', true))) if chat.photo and File.exist? self.format_content_link(chat.photo.small.remote.id, 'image.jpg', true) # cache userpic @xmpp.presence(chat_id.to_s, :subscribe, nil, nil, @cache[:chats][chat_id].title.to_s) if not no_subscription # send subscription request - @xmpp.presence(chat_id.to_s, nil, :chat, @cache[:chats][chat_id].title.to_s) if chat.type.instance_of? TD::Types::ChatType::BasicGroup or chat.type.instance_of? TD::Types::ChatType::Supergroup # send :chat status if its group/supergroup + @xmpp.presence(chat_id.to_s, nil, :chat, @cache[:chats][chat_id].title.to_s, nil, @cache[:userpics][chat_id]) if chat.type.instance_of? TD::Types::ChatType::BasicGroup or chat.type.instance_of? TD::Types::ChatType::Supergroup # send :chat status if its group/supergroup self.process_user_info(chat.type.user_id) if chat.type.instance_of? TD::Types::ChatType::Private # process user if its a private chat }.wait end @@ -345,7 +377,7 @@ class TelegramClient # convert telegram status to XMPP one def process_status_update(user_id, status) @logger.debug "Processing status update for user id %s.." % user_id.to_s - xmpp_show, xmpp_status = nil, '' + xmpp_show, xmpp_status, xmpp_photo = nil case status when TD::Types::UserStatus::Online xmpp_show = nil @@ -363,7 +395,8 @@ class TelegramClient xmpp_show = :unavailable xmpp_status = "Last seen last month" end - @xmpp.presence(user_id.to_s, nil, xmpp_show, xmpp_status) + xmpp_photo = @cache[:userpics][user_id] if @cache[:userpics].key? user_id + @xmpp.presence(user_id.to_s, nil, xmpp_show, xmpp_status, nil, xmpp_photo) end # get contact information (for vcard). @@ -385,17 +418,18 @@ class TelegramClient # userpic # if @cache[:chats][chat_id].photo then # we have userpic userpic = self.format_content_link(@cache[:chats][chat_id].photo.small.remote.id, 'image.jpg', true) - userpic = Base64.encode64(IO.binread(userpic)) if File.exist? userpic + userpic = (File.exist? userpic) ? Base64.encode64(IO.binread(userpic)) : nil end # .. - return title, username, firstname, lastname, phone, bio, userpic + return title, username, firstname, lastname, phone, bio, userpic end # roster status sync # - def sync_status() + def sync_status(user_id = nil) @logger.debug "Syncing statuses.." - @cache[:users].each_value do |user| process_status_update(user.id, user.status) end + if user_id and @cache[:users].key? user_id then return process_status_update(@cache[:users][user_id].id, @cache[:users][user_id].status) end # sync single contact # + @cache[:users].each_value do |user| process_status_update(user.id, user.status) end # sync everyone # end # graceful disconnect @@ -412,7 +446,7 @@ class TelegramClient # format tg user name # def format_username(user_id) - user_id = @me.id if user_id == 0 # @me + return if user_id == 0 # @me if not @cache[:users].key? user_id then self.process_user_info(user_id) end # update cache if not @cache[:users].key? user_id then return user_id end # return id if not found anything about this user id = (@cache[:users][user_id].username == '') ? user_id : @cache[:users][user_id].username # username or user id @@ -434,7 +468,7 @@ class TelegramClient text = '' @client.get_message(chat_id, message_id).then { |message| text = "%s" % message.content.text.text.to_s }.wait text = (text.lines.count > 1) ? "%s..." % text.split("\n")[0] : text - return "MSG %s (%s..)" % [message_id.to_s, text] + return "%s (%s..)" % [message_id.to_s, text] end # format content link # diff --git a/inc/xmppcomponent.rb b/inc/xmppcomponent.rb index 789ca8f..a1887e8 100644 --- a/inc/xmppcomponent.rb +++ b/inc/xmppcomponent.rb @@ -107,11 +107,13 @@ class XMPPComponent # vcard request # if iq.type == :get and iq.vcard and @sessions.key? iq.from.bare.to_s then @logger.debug "Got VCard request" - vcard = @sessions[iq.from.bare.to_s].make_vcard(iq.to.to_s) + vcard = @sessions[iq.from.bare.to_s].tg_contact_vcard(iq.to.to_s) reply = iq.answer reply.type = :result reply.elements["vCard"] = vcard @@transport.send(reply) + @sessions[iq.from.bare.to_s].tg_sync_roster(iq.to.to_s) # re-sync status + # time response # elsif iq.type == :result and iq.elements["time"] and @sessions.key? iq.from.bare.to_s then @logger.debug "Got Timezone response" @@ -203,7 +205,7 @@ class XMPPSession < XMPPComponent end # presence update # - def presence(from, type = nil, show = nil, status = nil, nickname = nil) + def presence(from, type = nil, show = nil, status = nil, nickname = nil, photo = nil) @logger.debug "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 @@ -212,6 +214,7 @@ class XMPPSession < XMPPComponent req.show = show unless show.nil? # presence req.status = status unless status.nil? # presence message 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 @logger.debug req @@transport.send(req) end @@ -230,9 +233,16 @@ class XMPPSession < XMPPComponent @logger.info "Authenticating in Telegram network with :%s" % typ @telegram.process_auth(typ, data) end + + # sync roster # + def tg_sync_roster(to = nil) + @logger.debug "Sync Telegram contact status with roster.. %s" % to.to_s + to = (to) ? to.split('@')[0].to_i : nil + @telegram.sync_status(to) + end # make vcard from telegram contact # - def make_vcard(to) + def tg_contact_vcard(to) @logger.debug "Requesting information to make a VCard for Telegram contact..." # title, username, firstname, lastname, phone, bio, userpic fn, nickname, given, family, phone, desc, photo = @telegram.get_contact_info(to.split('@')[0].to_i) vcard = Jabber::Vcard::IqVcard.new() @@ -255,6 +265,7 @@ class XMPPSession < XMPPComponent return vcard end + ########################################### ## timezones ## def request_tz(jid) @logger.debug "Request timezone from JID %s" % jid.to_s @@ -272,7 +283,7 @@ class XMPPSession < XMPPComponent @logger.debug "Set TZ to %s" % timezone @timezone = timezone @logger.debug "Resyncing contact list.." - @telegram.sync_status() + self.tg_sync_roster() end ###########################################