xasin-telegram 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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)