line-bot 0.1.7

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.
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