line-bot 0.1.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/CONTRIBUTING.md +15 -0
  3. data/LICENSE +202 -0
  4. data/README.md +269 -0
  5. data/lib/line/bot.rb +14 -0
  6. data/lib/line/bot/api.rb +8 -0
  7. data/lib/line/bot/api/errors.rb +9 -0
  8. data/lib/line/bot/api/version.rb +7 -0
  9. data/lib/line/bot/builder/multiple_message.rb +101 -0
  10. data/lib/line/bot/builder/rich_message.rb +139 -0
  11. data/lib/line/bot/client.rb +303 -0
  12. data/lib/line/bot/event_type.rb +15 -0
  13. data/lib/line/bot/httpclient.rb +31 -0
  14. data/lib/line/bot/message.rb +9 -0
  15. data/lib/line/bot/message/audio.rb +25 -0
  16. data/lib/line/bot/message/base.rb +35 -0
  17. data/lib/line/bot/message/contact.rb +18 -0
  18. data/lib/line/bot/message/content_type.rb +16 -0
  19. data/lib/line/bot/message/image.rb +23 -0
  20. data/lib/line/bot/message/location.rb +28 -0
  21. data/lib/line/bot/message/recipient_type.rb +9 -0
  22. data/lib/line/bot/message/sticker.rb +26 -0
  23. data/lib/line/bot/message/text.rb +22 -0
  24. data/lib/line/bot/message/video.rb +23 -0
  25. data/lib/line/bot/operation.rb +3 -0
  26. data/lib/line/bot/operation/added_as_friend.rb +10 -0
  27. data/lib/line/bot/operation/base.rb +17 -0
  28. data/lib/line/bot/operation/blocked_account.rb +10 -0
  29. data/lib/line/bot/operation/op_type.rb +10 -0
  30. data/lib/line/bot/receive/message.rb +69 -0
  31. data/lib/line/bot/receive/operation.rb +42 -0
  32. data/lib/line/bot/receive/request.rb +35 -0
  33. data/lib/line/bot/request.rb +86 -0
  34. data/lib/line/bot/response/user/contact.rb +22 -0
  35. data/lib/line/bot/response/user/profile.rb +30 -0
  36. data/lib/line/bot/utils.rb +16 -0
  37. data/line-bot-api.gemspec +32 -0
  38. data/line-bot.gemspec +32 -0
  39. metadata +193 -0
@@ -0,0 +1,14 @@
1
+ require 'line/bot/client'
2
+ require 'line/bot/api/errors'
3
+ require 'line/bot/api'
4
+ require 'line/bot/event_type'
5
+ require 'line/bot/message'
6
+ require 'line/bot/operation'
7
+ require 'line/bot/receive/message'
8
+ require 'line/bot/receive/operation'
9
+ require 'line/bot/receive/request'
10
+ require 'line/bot/response/user/profile'
11
+ require 'line/bot/request'
12
+ require 'line/bot/httpclient'
13
+ require 'line/bot/utils'
14
+ require 'line/bot/api/version'
@@ -0,0 +1,8 @@
1
+ module Line
2
+ module Bot
3
+ module API
4
+ DEFAULT_ENDPOINT = "https://trialbot-api.line.me/v1"
5
+ DEFAULT_SENDING_MESSAGE_CHANNEL_ID = 1383378250
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module Line
2
+ module Bot
3
+ module API
4
+ class Error < StandardError; end
5
+ class InvalidCredentialsError < Error; end
6
+ class NotSupportedError < Error; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ module Line
2
+ module Bot
3
+ module API
4
+ VERSION = "0.1.7"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,101 @@
1
+ require 'line/bot/message'
2
+
3
+ module Line
4
+ module Bot
5
+ module Builder
6
+ class MultipleMessage
7
+
8
+ def initialize(client)
9
+ @messages ||= []
10
+ @client = client
11
+ end
12
+
13
+ def add_text(attrs = {})
14
+ tap {
15
+ message = Message::Text.new(
16
+ text: attrs[:text],
17
+ )
18
+ push_message(message)
19
+ }
20
+ end
21
+
22
+ def add_image(attrs = {})
23
+ tap {
24
+ message = Message::Image.new(
25
+ image_url: attrs[:image_url],
26
+ preview_url: attrs[:preview_url],
27
+ )
28
+ push_message(message)
29
+ }
30
+ end
31
+
32
+ def add_video(attrs = {})
33
+ tap {
34
+ message = Message::Video.new(
35
+ video_url: attrs[:video_url],
36
+ preview_url: attrs[:preview_url],
37
+ )
38
+ push_message(message)
39
+ }
40
+ end
41
+
42
+ def add_audio(attrs = {})
43
+ tap {
44
+ message = Message::Audio.new(
45
+ audio_url: attrs[:audio_url],
46
+ duration: attrs[:duration],
47
+ )
48
+ push_message(message)
49
+ }
50
+ end
51
+
52
+ def add_location(attrs = {})
53
+ tap {
54
+ message = Message::Location.new(
55
+ title: attrs[:title],
56
+ latitude: attrs[:latitude],
57
+ longitude: attrs[:longitude],
58
+ )
59
+ push_message(message)
60
+ }
61
+ end
62
+
63
+ def add_sticker(attrs = {})
64
+ tap {
65
+ message = Message::Sticker.new(
66
+ stkpkgid: attrs[:stkpkgid],
67
+ stkid: attrs[:stkid],
68
+ stkver: attrs[:stkver],
69
+ )
70
+ push_message(message)
71
+ }
72
+ end
73
+
74
+ def push_message(message)
75
+ raise ArgumentError, "Invalid argument: `message`" unless message.valid?
76
+ @messages << message.content
77
+ end
78
+
79
+ def send(attrs = {})
80
+ @client.send_message(attrs[:to_mid], self)
81
+ end
82
+
83
+ def event_type
84
+ Line::Bot::EventType::MULTIPLE_MESSAGE
85
+ end
86
+
87
+ def content
88
+ {
89
+ messageNotified: 0,
90
+ messages: @messages
91
+ }
92
+ end
93
+
94
+ def valid?
95
+ @messages.size > 0
96
+ end
97
+
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,139 @@
1
+ require 'line/bot/message'
2
+ require 'json'
3
+
4
+ module Line
5
+ module Bot
6
+ module Builder
7
+ class RichMessage
8
+
9
+ def initialize(client)
10
+ @actions ||= {}
11
+ @listeners ||= []
12
+ @client = client
13
+ end
14
+
15
+ def set_action(attrs = {})
16
+ tap {
17
+ attrs.each { |key, value|
18
+ raise ArgumentError, 'Invalid arguments, :text, :link_url keys.' unless validate_action_attributes(value)
19
+
20
+ @actions[key.to_s] = {
21
+ type: 'web',
22
+ text: value[:text].to_s,
23
+ params: {
24
+ linkUri: value[:link_url].to_s
25
+ },
26
+ }
27
+ }
28
+ }
29
+ end
30
+
31
+ def validate_action_attributes(attrs = {})
32
+ attrs[:text] && attrs[:link_url]
33
+ end
34
+
35
+ def add_listener(attrs = {})
36
+ tap {
37
+ raise ArgumentError, 'Invalid arguments, :x, :y, :width, :height keys.' unless validate_listener_attributes(attrs)
38
+
39
+ listener = {
40
+ type: 'touch', # Fixed "touch".
41
+ params: [attrs[:x].to_i, attrs[:y].to_i, attrs[:width].to_i, attrs[:height].to_i],
42
+ action: attrs[:action].to_s,
43
+ }
44
+ @listeners << listener
45
+ }
46
+ end
47
+
48
+ def validate_listener_attributes(attrs = {})
49
+ attrs[:action] &&
50
+ attrs[:x] &&
51
+ attrs[:y] &&
52
+ attrs[:width] &&
53
+ attrs[:height]
54
+ end
55
+
56
+ # send rich message to line server and to users
57
+ #
58
+ # @param attrs [Hash]
59
+ # @param attrs [:to_mid] [String or Array] line user's mids
60
+ # @param attrs [:image_url] [String] image file's url
61
+ # @param attrs [:alt_text] [String] alt text for image file's url
62
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :image_url, :preview_url keys.
63
+ #
64
+ # @return [Net::HTTPResponse] response for a request to line server
65
+ def send(attrs = {})
66
+ @image_url = attrs[:image_url]
67
+ @alt_text = attrs[:alt_text]
68
+
69
+ @client.send_message(attrs[:to_mid], self)
70
+ end
71
+
72
+ def height
73
+ height = 0
74
+ @listeners.each { |listener|
75
+ h = listener[:params][1] + listener[:params][3] # params.y + params.height
76
+ height = h if height < h
77
+ }
78
+
79
+ height > 2080 ? 2080 : height
80
+ end
81
+
82
+ def event_type
83
+ Line::Bot::EventType::MESSAGE
84
+ end
85
+
86
+ def content
87
+ {
88
+ contentType: Line::Bot::Message::ContentType::RICH_MESSAGE,
89
+ toType: Line::Bot::Message::RecipientType::USER,
90
+ contentMetadata: {
91
+ DOWNLOAD_URL: @image_url,
92
+ SPEC_REV: "1", # Fixed "1".
93
+ ALT_TEXT: @alt_text,
94
+ MARKUP_JSON: markup.to_json,
95
+ },
96
+ }
97
+ end
98
+
99
+ def valid?
100
+ @image_url && @alt_text
101
+ end
102
+
103
+ def markup
104
+ {
105
+ canvas: {
106
+ height: height,
107
+ width: 1040, # Integer fixed value: 1040
108
+ initialScene: 'scene1',
109
+ },
110
+ images: {
111
+ image1: {
112
+ x: 0,
113
+ y: 0,
114
+ w: 1040, # Integer fixed value: 1040
115
+ h: height
116
+ },
117
+ },
118
+ actions: @actions,
119
+ scenes: {
120
+ scene1: {
121
+ draws: [
122
+ {
123
+ image: 'image1', # Use the image ID "image1".
124
+ x: 0,
125
+ y: 0,
126
+ w: 1040, # Integer fixed value: 1040
127
+ h: height
128
+ },
129
+ ],
130
+ listeners: @listeners
131
+ }
132
+ }
133
+ }
134
+ end
135
+
136
+ end
137
+ end
138
+ end
139
+ end
@@ -0,0 +1,303 @@
1
+ require 'line/bot/message'
2
+ require 'line/bot/request'
3
+ require 'line/bot/builder/rich_message'
4
+ require 'line/bot/builder/multiple_message'
5
+ require 'line/bot/api/errors'
6
+ require 'base64'
7
+ require 'net/http'
8
+
9
+ module Line
10
+ module Bot
11
+ class Client
12
+
13
+ include Line::Bot::Utils
14
+
15
+ # @return [String]
16
+ attr_accessor :channel_id, :channel_secret, :channel_mid, :endpoint, :to_channel_id, :httpclient
17
+
18
+ # Initialize a new Bot Client.
19
+ #
20
+ # @param options [Hash]
21
+ #
22
+ # @return [Line::Bot::Client]
23
+ def initialize(options = {})
24
+ options.each do |key, value|
25
+ instance_variable_set("@#{key}", value)
26
+ end
27
+ yield(self) if block_given?
28
+
29
+ @httpclient ||= Line::Bot::HTTPClient.new
30
+ end
31
+
32
+ def endpoint
33
+ @endpoint ||= Line::Bot::API::DEFAULT_ENDPOINT
34
+ end
35
+
36
+ def to_channel_id
37
+ @to_channel_id ||= Line::Bot::API::DEFAULT_SENDING_MESSAGE_CHANNEL_ID
38
+ end
39
+
40
+ # @return [Hash]
41
+ def credentials
42
+ {
43
+ 'X-Line-ChannelID' => channel_id,
44
+ 'X-Line-ChannelSecret' => channel_secret,
45
+ 'X-Line-Trusted-User-With-ACL' => channel_mid,
46
+ }
47
+ end
48
+
49
+ def credentials?
50
+ credentials.values.all?
51
+ end
52
+
53
+ # Send text to users.
54
+ #
55
+ # @param attrs [Hash]
56
+ # @param to_mid [String or Array] User's identifiers
57
+ # @param text [String]
58
+ #
59
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :text keys.
60
+ #
61
+ # @return [Net::HTTPResponse]
62
+ def send_text(attrs = {})
63
+ message = Message::Text.new(
64
+ text: attrs[:text],
65
+ )
66
+ send_message(attrs[:to_mid], message)
67
+ end
68
+
69
+ # Send image to users.
70
+ #
71
+ # @param attrs [Hash]
72
+ # @param to_mid [String or Array] User's identifiers
73
+ # @param image_url [String] Image file's url
74
+ # @param preview_url [String] Preview image file's url
75
+ #
76
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :image_url, :preview_url keys.
77
+ #
78
+ # @return [Net::HTTPResponse]
79
+ def send_image(attrs = {})
80
+ message = Message::Image.new(
81
+ image_url: attrs[:image_url],
82
+ preview_url: attrs[:preview_url],
83
+ )
84
+ send_message(attrs[:to_mid], message)
85
+ end
86
+
87
+ # Send video to users.
88
+ #
89
+ # @param attrs [Hash]
90
+ # @param to_mid [String or Array] User's identifiers
91
+ # @param video_url [String] Video file's url
92
+ # @param preview_url [String] Preview image file's url
93
+ #
94
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :video_url, :preview_url keys.
95
+ #
96
+ # @return [Net::HTTPResponse]
97
+ def send_video(attrs = {})
98
+ message = Message::Video.new(
99
+ video_url: attrs[:video_url],
100
+ preview_url: attrs[:preview_url],
101
+ )
102
+ send_message(attrs[:to_mid], message)
103
+ end
104
+
105
+ # Send audio to users.
106
+ #
107
+ # @param attrs [Hash]
108
+ # @param to_mid [String or Array] User's identifiers
109
+ # @param audio_url [String] Audio file's url
110
+ # @param duration [String or Integer] Voice message's length, milliseconds
111
+ #
112
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :audio_url, :duration keys.
113
+ #
114
+ # @return [Net::HTTPResponse]
115
+ def send_audio(attrs = {})
116
+ message = Message::Audio.new(
117
+ audio_url: attrs[:audio_url],
118
+ duration: attrs[:duration],
119
+ )
120
+ send_message(attrs[:to_mid], message)
121
+ end
122
+
123
+ # Send location to users.
124
+ #
125
+ # @param attrs [Hash]
126
+ # @param to_mid [String or Array] User's identifiers
127
+ # @param title [String] Location's title
128
+ # @param address [String] Location's address
129
+ # @param latitude [Float] Location's latitude
130
+ # @param longitude [Float] Location's longitude
131
+ #
132
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :title, :latitude, :longitude keys.
133
+ #
134
+ # @return [Net::HTTPResponse]
135
+ def send_location(attrs = {})
136
+ message = Message::Location.new(
137
+ title: attrs[:title],
138
+ address: attrs[:address],
139
+ latitude: attrs[:latitude],
140
+ longitude: attrs[:longitude],
141
+ )
142
+ send_message(attrs[:to_mid], message)
143
+ end
144
+
145
+ # Send sticker to users.
146
+ #
147
+ # @param attrs [Hash]
148
+ # @param to_mid [String or Array] User's identifiers
149
+ # @param stkpkgid [String or Integer] Sticker's package identifier
150
+ # @param stkid [String or Integer] Sticker's identifier
151
+ # @param stkver [String or Integer] Sticker's version number
152
+ #
153
+ # @raise [ArgumentError] Error raised when supplied argument are missing :to_mid, :stkpkgid, :stkid, :stkver keys.
154
+ #
155
+ # @return [Net::HTTPResponse]
156
+ def send_sticker(attrs = {})
157
+ message = Message::Sticker.new(
158
+ stkpkgid: attrs[:stkpkgid],
159
+ stkid: attrs[:stkid],
160
+ stkver: attrs[:stkver],
161
+ )
162
+ send_message(attrs[:to_mid], message)
163
+ end
164
+
165
+ # Send message to line server and to users.
166
+ #
167
+ # @param to_mid [String or Array] User's identifiers
168
+ # @param message [Line::Bot::Message]
169
+ #
170
+ # @raise [ArgumentError] Error raised when supplied argument are missing message.
171
+ #
172
+ # @return [Net::HTTPResponse]
173
+ def send_message(to_mid, message)
174
+ raise Line::Bot::API::InvalidCredentialsError, 'Invalidates credentials' unless credentials?
175
+
176
+ request = Request.new do |config|
177
+ config.to_channel_id = to_channel_id
178
+ config.httpclient = httpclient
179
+ config.endpoint = endpoint
180
+ config.endpoint_path = '/events'
181
+ config.credentials = credentials
182
+ config.to_mid = to_mid
183
+ config.message = message
184
+ end
185
+
186
+ request.post
187
+ end
188
+
189
+ # Get message content.
190
+ #
191
+ # @param identifier [String] Message's identifier
192
+ #
193
+ # @raise [ArgumentError] Error raised when supplied argument are missing message.
194
+ #
195
+ # @return [Net::HTTPResponse]
196
+ def get_message_content(identifier)
197
+ endpoint_path = "/bot/message/#{identifier}/content"
198
+ get(endpoint_path)
199
+ end
200
+
201
+ # Get preview of message content.
202
+ #
203
+ # @param identifier [String] Message's identifier
204
+ #
205
+ # @raise [ArgumentError] Error raised when supplied argument are missing message.
206
+ #
207
+ # @return [Net::HTTPResponse]
208
+ def get_message_content_preview(identifier)
209
+ endpoint_path = "/bot/message/#{identifier}/content/preview"
210
+ get(endpoint_path)
211
+ end
212
+
213
+ # Get user profile.
214
+ #
215
+ # @param mids [String or Array] User's identifiers
216
+ #
217
+ # @raise [ArgumentError] Error raised when supplied argument are missing message.
218
+ # @raise [HTTPError]
219
+ #
220
+ # @return [Line::Bot::Response::User::Profile]
221
+ def get_user_profile(mids)
222
+ raise ArgumentError, 'Wrong argument type `mids`' unless validate_mids(mids)
223
+
224
+ query = mids.is_a?(Array) ? mids.join(',') : mids
225
+ endpoint_path = "/profiles?mids=#{query}"
226
+
227
+ response = get(endpoint_path)
228
+
229
+ Line::Bot::Response::User::Profile.new(response) if !response.value
230
+ end
231
+
232
+ # Fetch data, get content of specified URL.
233
+ #
234
+ # @param endpoint_path [String]
235
+ #
236
+ # @return [Net::HTTPResponse]
237
+ def get(endpoint_path)
238
+ raise Line::Bot::API::InvalidCredentialsError, 'Invalidates credentials' unless credentials?
239
+
240
+ request = Request.new do |config|
241
+ config.to_channel_id = to_channel_id
242
+ config.httpclient = httpclient
243
+ config.endpoint = endpoint
244
+ config.endpoint_path = endpoint_path
245
+ config.credentials = credentials
246
+ end
247
+
248
+ request.get
249
+ end
250
+
251
+ # Create rich message to line server and to users.
252
+ #
253
+ # @return [Line::Bot::Builder::RichMessage]
254
+ def rich_message
255
+ Line::Bot::Builder::RichMessage.new(self)
256
+ end
257
+
258
+ # Create multiple message to line server and to users.
259
+ #
260
+ # @return [Line::Bot::Builder::MultipleMessage]
261
+ def multiple_message
262
+ Line::Bot::Builder::MultipleMessage.new(self)
263
+ end
264
+
265
+ # Validate signature
266
+ #
267
+ # @param content [String] Request's body
268
+ # @param channel_signature [String] Request'header 'X-LINE-ChannelSignature' # HTTP_X_LINE_CHANNELSIGNATURE
269
+ #
270
+ # @return [Boolean]
271
+ def validate_signature(content = "", channel_signature)
272
+ return false unless !channel_signature.nil? && credentials?
273
+
274
+ hash = OpenSSL::HMAC::digest(OpenSSL::Digest::SHA256.new, channel_secret, content)
275
+ signature = Base64.strict_encode64(hash)
276
+
277
+ variable_secure_compare(channel_signature, signature)
278
+ end
279
+
280
+ private
281
+ # Constant time string comparison.
282
+ #
283
+ # via timing attacks.
284
+ # reference: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/security_utils.rb
285
+ # @return [Boolean]
286
+ def variable_secure_compare(a, b)
287
+ secure_compare(::Digest::SHA256.hexdigest(a), ::Digest::SHA256.hexdigest(b))
288
+ end
289
+
290
+ # @return [Boolean]
291
+ def secure_compare(a, b)
292
+ return false unless a.bytesize == b.bytesize
293
+
294
+ l = a.unpack "C#{a.bytesize}"
295
+
296
+ res = 0
297
+ b.each_byte { |byte| res |= byte ^ l.shift }
298
+ res == 0
299
+ end
300
+ end
301
+
302
+ end
303
+ end