xasin-telegram 0.2.3 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,162 +1,26 @@
1
1
 
2
- require_relative 'HTTPCore.rb'
2
+ require_relative 'GroupingAdapter.rb'
3
3
  require 'mqtt/sub_handler'
4
4
 
5
5
  module Xasin
6
6
  module Telegram
7
7
  module MQTT
8
- class Server
8
+ class Server < GroupingAdapter
9
9
  attr_accessor :usernameList
10
10
 
11
11
  def initialize(httpCore, mqtt)
12
- # Check if we already have a HTTPCore, else create one
13
- if(httpCore.is_a? Telegram::HTTPCore)
14
- @httpCore = httpCore;
15
- else
16
- @httpCore = Telegram::HTTPCore.new(httpCore);
17
- end
18
- @httpCore.attach_receptor(self);
12
+ super(httpCore);
19
13
 
20
14
  @mqtt = mqtt;
21
-
22
- # Hash {username => ChatID}
23
- @usernameList = Hash.new();
24
- # Hash {ChatID => {GroupID => MessageID}}
25
- @groupIDList = Hash.new do |hash, key|
26
- hash[key] = Hash.new;
27
- end
28
-
29
15
  setup_mqtt();
30
16
  end
31
17
 
32
- def _process_inline_keyboard(keyboardLayout, gID = nil)
33
- # Return unless we have a structure we can form into a keyboard
34
- return nil unless (keyboardLayout.is_a? Array or keyboardLayout.is_a? Hash)
35
-
36
- # Make sure the structure of keyboardLayout is [{}] or [[]]
37
- if(keyboardLayout.is_a? Hash)
38
- keyboardLayout = [keyboardLayout]
39
- elsif(not (keyboardLayout[0].is_a? Array or keyboardLayout[0].is_a? Hash))
40
- keyboardLayout = [keyboardLayout]
41
- end
42
-
43
- outData = Array.new();
44
-
45
- # Iterate through the rows of keyboards
46
- keyboardLayout.each do |row|
47
- newRow = Array.new();
48
-
49
- # Create the INLINE KEY button elements
50
- row.each do |key, val|
51
- cbd = {i: gID, k: (val or key)};
52
- newRow << {text: key, callback_data: cbd.to_json}
53
- end
54
-
55
- # Add the new row to the array of rows
56
- outData << newRow;
57
- end
58
-
59
- # Return the ready reply_markup element
60
- return {inline_keyboard: outData};
61
- end
62
-
63
- # Processes messages received through MQTT
64
- # It takes care of setting a few good defaults (like parse_mode),
65
- # deletes any old messages of the same GroupID (if requested),
66
- # and stores the new Message ID for later processing
67
- # @param data [Hash] The raw "message" object received from the Telegram API
68
- # @param uID [Integer,String] The user-id as received from the MQTT Wildcard.
69
- # Can be a username defined in @usernameList
70
- def _handle_send(data, uID)
71
- # Resolve a saved Username to a User-ID
72
- uID = @usernameList[uID] if(@usernameList.key? uID)
73
- return if (uID = uID.to_i) == 0; # Return if a unknown Username was used
74
-
75
- gID = data[:gid];
76
-
77
- # Check if a GroupID is present and a former message is known
78
- if(gID and @groupIDList[uID][gID])
79
- if(data[:replace])
80
- _handle_delete(gID, uID)
81
- elsif(data[:overwrite])
82
- _handle_edit(data, uID);
83
- return; # After editing, no new message should be sent!
84
- end
85
- end
86
-
87
- # Lay out all mandatory parameters for sendMessage request
88
- outData = {
89
- chat_id: uID,
90
- parse_mode: (data[:parse_mode] or "Markdown"), # Markdown parse mode is just nice
91
- text: data[:text]
92
- }
93
-
94
- # Check if the message is meant to be sent without notification
95
- if(data[:silent])
96
- outData[:disable_notification] = true;
97
- end
98
-
99
- # Check if an inline keyboard layout is given, and parse that.
100
- if((ilk = data[:inline_keyboard]))
101
- outData[:reply_markup] = _process_inline_keyboard(ilk, gID);
102
- end
103
-
104
- reply = @httpCore.perform_post("sendMessage", outData);
105
- return unless reply[:ok] # Something was wrong about our message layout
106
- # TODO Add a proper error handler here.
107
-
108
- # If a GroupID was given, save the sent message's ID
109
- @groupIDList[uID][gID] = reply[:result][:message_id] if(gID);
110
- end
111
-
112
- def _handle_edit(data, uID)
113
- # Resolve a saved Username to a User-ID
114
- uID = @usernameList[uID] if(@usernameList.key? uID)
115
- return if (uID = uID.to_i) == 0; # Return if a unknown Username was used
116
-
117
- # Fetch the target MessageID - Return if none is present
118
- return unless mID = @groupIDList[uID][data[:gid]]
119
-
120
- # Lay out all mandatory arguments for the edit POST
121
- outData = {
122
- chat_id: uID,
123
- message_id: mID,
124
- };
125
-
126
- # If a inline keyboard was given, parse that.
127
- if(ilk = data[:inline_keyboard])
128
- outData[:reply_markup] = _process_inline_keyboard(ilk, data[:gid]);
129
- end
130
-
131
- if(data[:text]) # Check if text was given
132
- outData[:text] = data[:text];
133
- # Send the POST request editing the message text
134
- @httpCore.perform_post("editMessageText", outData);
135
- else
136
- # Otherwise, only edit the reply markup (keyboard etc.)
137
- @httpCore.perform_post("editMessageReplyMarkup", outData);
138
- end
139
- end
140
-
141
- def _handle_delete(data, uID)
142
- # Resolve a saved Username to a User-ID
143
- uID = @usernameList[uID] if(@usernameList.key? uID)
144
- return if (uID = uID.to_i) == 0; # Return unless the username was known
145
-
146
- # Fetch the real message ID held by a grouping ID
147
- return unless mID = @groupIDList[uID][data]
148
- @groupIDList[uID].delete(data); # Clear that ID from the list
149
-
150
- # Perform the actual delete
151
- @httpCore.perform_post("deleteMessage", {chat_id: uID, message_id: mID});
152
- end
153
-
154
18
  def setup_mqtt()
155
19
  @mqtt.subscribe_to "Telegram/+/Send" do |data, tSplit|
156
20
  begin
157
- data = JSON.parse(data);
21
+ data = JSON.parse(data, symbolize_names: true);
158
22
  rescue
159
- data = {text: data}
23
+ data = { text: data }
160
24
  end
161
25
 
162
26
  _handle_send(data, tSplit[0]);
@@ -164,7 +28,7 @@ module Telegram
164
28
 
165
29
  @mqtt.subscribe_to "Telegram/+/Edit" do |data, tSplit|
166
30
  begin
167
- data = JSON.parse(data);
31
+ data = JSON.parse(data, symbolize_names: true);
168
32
  rescue
169
33
  next;
170
34
  end
@@ -187,52 +51,21 @@ module Telegram
187
51
  end
188
52
  end
189
53
 
190
- def handle_packet(packet)
191
- if(msg = packet[:message])
192
- uID = msg[:chat][:id];
193
- if(newUID = @usernameList.key(uID))
194
- uID = newUID
195
- end
196
-
197
- data = Hash.new();
198
- return unless(data[:text] = msg[:text])
199
-
200
- if(replyMSG = msg[:reply_to_message])
201
- data[:reply_gid] = @groupIDList[uID].key(replyMSG[:message_id]);
202
- end
203
-
204
- if(data[:text] =~ /^\//)
205
- @mqtt.publish_to "Telegram/#{uID}/Command", data.to_json;
206
- elsif(data[:reply_gid])
207
- @mqtt.publish_to "Telegram/#{uID}/Reply", data.to_json;
208
- else
209
- @mqtt.publish_to "Telegram/#{uID}/Received", data.to_json;
210
- end
211
- end
212
-
213
- if(msg = packet[:callback_query])
214
- @httpCore.perform_post("answerCallbackQuery", {callback_query_id: msg[:id]});
215
-
216
- uID = msg[:message][:chat][:id];
217
- if(newUID = @usernameList.key(uID))
218
- uID = newUID
219
- end
220
-
221
- begin
222
- data = JSON.parse(msg[:data], symbolize_names: true);
223
-
224
- data = {
225
- gid: data[:i],
226
- key: data[:k],
227
- }
228
-
229
- if(data[:key] =~ /^\//)
230
- @mqtt.publish_to "Telegram/#{uID}/Command", {text: data[:key]}.to_json
231
- end
232
- @mqtt.publish_to "Telegram/#{uID}/KeyboardPress", data.to_json
233
- rescue
234
- end
235
- end
54
+ def on_message(data, uID)
55
+ super(data, uID);
56
+ @mqtt.publish_to "Telegram/#{uID}/Received", data.to_json;
57
+ end
58
+ def on_command(data, uID)
59
+ super(data, uID);
60
+ @mqtt.publish_to "Telegram/#{uID}/Command", data.to_json;
61
+ end
62
+ def on_reply(data, uID)
63
+ super(data, uID);
64
+ @mqtt.publish_to "Telegram/#{uID}/Reply", data.to_json;
65
+ end
66
+ def on_callback_pressed(data, uID)
67
+ super(data, uID);
68
+ @mqtt.publish_to "Telegram/#{uID}/KeyboardPress", data.to_json
236
69
  end
237
70
  end
238
71
  end
@@ -0,0 +1,126 @@
1
+
2
+ module Xasin
3
+ module Telegram
4
+ class Message
5
+ # Return whether or not parsing of the message was successful.
6
+ # TODO Actually perfom validity checks.
7
+ attr_reader :valid
8
+
9
+ # Returns the message ID of this message.
10
+ attr_reader :message_id
11
+ # Returns the {Chat} object this message was sent in.
12
+ attr_reader :chat
13
+ # Returns the {User} Object that sent this message.
14
+ attr_reader :user
15
+
16
+ # Optional argument, ID of the message that was replied to.
17
+ attr_reader :reply_to_id
18
+
19
+ # String, text of the message.
20
+ # Even if the message is not a String (i.e. a Sticker etc.),
21
+ # this will be at least an empty string.
22
+ attr_reader :text
23
+
24
+ # Timestamp, from Telegram, that the message was sent on.
25
+ attr_reader :timestamp
26
+
27
+ # Optional, command included in this message.
28
+ # Can be nil.
29
+ attr_reader :command
30
+
31
+ # Whether a command already handled this message.
32
+ # Usually means that it should not be processed any further,
33
+ # in order to prevent multiple commands from acting on the same
34
+ # message and causing weird behaviors.
35
+ attr_accessor :handled
36
+
37
+ # Initialize a message object.
38
+ # This will create a new message object with the given
39
+ # Telegram "Message" Hash. It can be taken directly from
40
+ # the Telegram API.
41
+ #
42
+ # The Message will automatically try to fetch the matching
43
+ # {Chat} and {User} that sent the message, and will also
44
+ # parse any additional metadata such as commands, stickers,
45
+ # etc.
46
+ def initialize(handler, message_object)
47
+ @handler = handler
48
+
49
+ return if message_object.nil?
50
+
51
+ @valid = true;
52
+
53
+ @message_id = message_object[:message_id]
54
+
55
+ @chat = handler[message_object[:chat]]
56
+ @user = handler[message_object[:from]]
57
+
58
+ @reply_to_id = message_object.dig(:reply_to_message, :id)
59
+
60
+ @text = message_object[:text] || "";
61
+
62
+ @timestamp = Time.at(message_object[:date] || 0)
63
+
64
+ m = /\/([\S]*)/.match(@text)
65
+ @command = m[1] if m
66
+
67
+ @handled = false
68
+ end
69
+
70
+ # Edit the text of the message.
71
+ # Simple wrapper for the 'editMessageText' Telegram
72
+ # API function, and will directly set the messge's text.
73
+ #
74
+ # parse_mode is set to HTML.
75
+ def edit_text(text)
76
+ out_data = {
77
+ chat_id: @chat.chat_id,
78
+ message_id: @message_id,
79
+ parse_mode: 'HTML',
80
+ text: text
81
+ }
82
+
83
+ @handler.core.perform_post('editMessageText', out_data);
84
+ end
85
+ alias text= edit_text
86
+
87
+ # Try to delete this message.
88
+ # Wrapper for Telegram's deleteMessage function
89
+ def delete!
90
+ @handler.core.perform_post('deleteMessage',
91
+ {
92
+ chat_id: @chat.chat_id,
93
+ message_id: @message_id
94
+ }
95
+ );
96
+ end
97
+
98
+ # Send a text message with it's reply set to this.
99
+ #
100
+ # This will send a new message with given text, whose reply
101
+ # message ID is set to this message. Makes it easy to respond to
102
+ # certain events quite cleanly.
103
+ def reply(text)
104
+ @chat.send_message(text, reply_to: self)
105
+ end
106
+
107
+ # Send a message to the chat this message originated from.
108
+ #
109
+ # This is a wrapper for message.chat.send_message, as it allows
110
+ # the Bot to easily respond to a {User}'s action in the same chat
111
+ # the user wrote it in. It will simply forward all arguments to
112
+ # {Chat#send_message}
113
+ def send_message(text, **opts)
114
+ @chat.send_message(text, **opts)
115
+ end
116
+
117
+ def to_s
118
+ @text
119
+ end
120
+
121
+ def to_i
122
+ @message_id
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,34 @@
1
+
2
+
3
+ module Xasin
4
+ module Telegram
5
+ class OnCommand < OnTelegramEvent
6
+ attr_accessor :deny_message
7
+ attr_accessor :required_perms
8
+
9
+ def initialize(options)
10
+ super()
11
+
12
+ @block = options[:block]
13
+ @command = options[:command]
14
+
15
+ @required_perms = [options[:permissions]].flatten.uniq
16
+
17
+ @deny_message = options[:deny_msg] || 'You are not authorized, %s.'
18
+
19
+ @priority += 5
20
+ end
21
+
22
+ def nomp_message(message)
23
+ return if message.handled
24
+ return unless message.command == @command
25
+
26
+ if message.user.has_permissions? @required_perms
27
+ @block.call message
28
+ else
29
+ message.reply @deny_message % [message.user.casual_name]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+
2
+
3
+ module Xasin
4
+ module Telegram
5
+ class OnMessage < OnTelegramEvent
6
+ def initialize(options)
7
+ super()
8
+
9
+ @block = options[:block]
10
+ @regexp = options[:regexp]
11
+
12
+ @priority += 1 if @regexp
13
+ end
14
+
15
+ def nomp_message(message)
16
+ if @regexp
17
+ match = @regexp.match message.to_s
18
+
19
+ if match
20
+ @block.call message, match
21
+ message.handled = true
22
+ end
23
+ else
24
+ @block.call message
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -7,7 +7,13 @@ module Telegram
7
7
  attr_reader :httpCore
8
8
 
9
9
  def initialize(userChat, httpCore)
10
- @httpCore = httpCore.is_a?(Telegram::HTTPCore) ? httpCore : HTTPCore.new(httpCore);
10
+ # Check if we already have a HTTPCore, else create one
11
+ @httpCore = if(httpCore.is_a? Telegram::HTTPCore)
12
+ httpCore;
13
+ else
14
+ Telegram::HTTPCore.new(httpCore);
15
+ end
16
+ @httpCore.attach_receptor(self);
11
17
  @httpCore.attach_receptor(self);
12
18
 
13
19
  @userID = userChat;
@@ -17,19 +23,18 @@ module Telegram
17
23
  end
18
24
 
19
25
  def handle_packet(packet)
20
- if(packet[:message]) then
26
+ if(packet[:message])
21
27
  return unless packet[:message][:chat][:id] == @userID;
22
28
 
23
29
  @message_procs.each do |cb| cb.call(packet[:message]); end
24
30
  packet[:has_been_handled] = true;
25
31
  end
26
32
 
27
- if(packet[:callback_query]) then
28
- return unless packet[:callback_query][:message][:chat][:id] == @userID;
33
+ return unless packet[:callback_query]
34
+ return unless packet[:callback_query][:message][:chat][:id] == @userID;
29
35
 
30
- @inlinebutton_procs.each do |cb| cb.call(packet[:callback_query]); end
31
- packet[:has_been_handled] = true;
32
- end
36
+ @inlinebutton_procs.each { |cb| cb.call(packet[:callback_query]); }
37
+ packet[:has_been_handled] = true;
33
38
  end
34
39
 
35
40
  def send_message(text, **args)