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.
- checksums.yaml +4 -4
- data/README.md +24 -1
- data/lib/xasin/bad_config.yml +7 -0
- data/lib/xasin/bad_test.rb +38 -0
- data/lib/xasin/telegram.rb +1 -0
- data/lib/xasin/telegram/Chat.rb +87 -0
- data/lib/xasin/telegram/GroupingAdapter.rb +273 -0
- data/lib/xasin/telegram/HTTPCore.rb +75 -22
- data/lib/xasin/telegram/Handler.rb +301 -0
- data/lib/xasin/telegram/KeyboardLayout.rb +67 -0
- data/lib/xasin/telegram/MQTT_Adapter.rb +21 -188
- data/lib/xasin/telegram/Message.rb +126 -0
- data/lib/xasin/telegram/OnCommand.rb +34 -0
- data/lib/xasin/telegram/OnMessage.rb +29 -0
- data/lib/xasin/telegram/SingleUser.rb +12 -7
- data/lib/xasin/telegram/TestHTTPCore.rb +30 -8
- data/lib/xasin/telegram/User.rb +142 -0
- metadata +14 -3
@@ -1,162 +1,26 @@
|
|
1
1
|
|
2
|
-
require_relative '
|
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
|
-
|
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
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
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])
|
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
|
-
|
28
|
-
|
33
|
+
return unless packet[:callback_query]
|
34
|
+
return unless packet[:callback_query][:message][:chat][:id] == @userID;
|
29
35
|
|
30
|
-
|
31
|
-
|
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)
|