line-message-builder 0.9.0 → 0.10.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.release-please-manifest.json +1 -1
  3. data/.rubocop.yml +5 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CLAUDE.md +124 -0
  6. data/README.md +24 -16
  7. data/claudekit.json +11 -0
  8. data/docs/rubrics/rdoc.md +169 -0
  9. data/lib/line/message/builder/actions/message.rb +47 -26
  10. data/lib/line/message/builder/actions/postback.rb +56 -26
  11. data/lib/line/message/builder/actions/uri.rb +139 -0
  12. data/lib/line/message/builder/actions.rb +6 -3
  13. data/lib/line/message/builder/base.rb +92 -67
  14. data/lib/line/message/builder/container.rb +55 -60
  15. data/lib/line/message/builder/context.rb +51 -72
  16. data/lib/line/message/builder/flex/actionable.rb +53 -32
  17. data/lib/line/message/builder/flex/box.rb +342 -79
  18. data/lib/line/message/builder/flex/bubble.rb +73 -46
  19. data/lib/line/message/builder/flex/builder.rb +39 -28
  20. data/lib/line/message/builder/flex/button.rb +90 -42
  21. data/lib/line/message/builder/flex/carousel.rb +48 -20
  22. data/lib/line/message/builder/flex/icon.rb +152 -0
  23. data/lib/line/message/builder/flex/image.rb +95 -33
  24. data/lib/line/message/builder/flex/partial.rb +47 -49
  25. data/lib/line/message/builder/flex/position.rb +187 -100
  26. data/lib/line/message/builder/flex/separator.rb +49 -6
  27. data/lib/line/message/builder/flex/size.rb +67 -47
  28. data/lib/line/message/builder/flex/span.rb +106 -32
  29. data/lib/line/message/builder/flex/text.rb +166 -54
  30. data/lib/line/message/builder/flex.rb +26 -23
  31. data/lib/line/message/builder/quick_reply.rb +174 -4
  32. data/lib/line/message/builder/text.rb +70 -3
  33. data/lib/line/message/builder/version.rb +1 -1
  34. data/lib/line/message/builder.rb +14 -11
  35. data/lib/line/message/rspec/matchers/have_flex_bubble.rb +1 -1
  36. data/lib/line/message/rspec/matchers/have_flex_component.rb +16 -6
  37. data/lib/line/message/rspec/matchers/have_flex_message.rb +1 -1
  38. data/lib/line/message/rspec/matchers/have_flex_separator.rb +1 -1
  39. data/lib/line/message/rspec/matchers/have_quick_reply.rb +1 -1
  40. data/lib/line/message/rspec/matchers/have_text_message.rb +1 -1
  41. data/llm.txt +276 -35
  42. data/release-please-config.json +3 -1
  43. metadata +9 -3
@@ -4,18 +4,19 @@ module Line
4
4
  module Message
5
5
  module Builder
6
6
  module Actions
7
- # Represents a "postback action" for LINE messages.
7
+ # Represents a postback action for LINE messages.
8
8
  #
9
9
  # A postback action sends a postback event to your bot's webhook when a
10
10
  # button associated with this action is tapped. The event contains the
11
- # specified `data` payload. Optionally, `displayText` can be provided,
11
+ # specified +data+ payload. Optionally, +display_text+ can be provided,
12
12
  # which will be shown in the chat as a message from the user.
13
13
  #
14
14
  # This action is useful for triggering specific backend logic or flows
15
15
  # without necessarily displaying a message in the chat, or for displaying
16
16
  # a different message than the data payload.
17
17
  #
18
- # @example Creating a postback action for a quick reply button
18
+ # == Example
19
+ #
19
20
  # Line::Message::Builder.with do
20
21
  # text "What do you want to do?"
21
22
  # quick_reply do
@@ -26,39 +27,61 @@ module Line
26
27
  # end
27
28
  # end
28
29
  #
29
- # @see https://developers.line.biz/en/reference/messaging-api/#postback-action
30
+ # See also:
31
+ # - https://developers.line.biz/en/reference/messaging-api/#postback-action
30
32
  class Postback < Line::Message::Builder::Base
31
- # @!attribute [r] data
32
- # @return [String] The data payload to be sent in the postback event
33
- # to the webhook. This is a required attribute. Max 300 characters.
33
+ # The data payload to be sent in the postback event to the webhook.
34
+ # This is a required attribute. Max 300 characters.
34
35
  attr_reader :data
35
36
 
36
- # Defines an optional `label` for the action.
37
+ # :method: label
38
+ # :call-seq:
39
+ # label() -> String or nil
40
+ # label(value) -> String or nil
41
+ #
42
+ # Sets or gets the label for the action.
43
+ #
37
44
  # The label is recommended by LINE for accessibility. For some message
38
45
  # types (e.g., buttons), the button's label itself is used.
39
46
  #
40
- # @!method label(value = nil)
41
- # @param value [String, nil] The label text for the action.
42
- # @return [String, nil] The current label text.
47
+ # [value]
48
+ # The label text for the action
43
49
  option :label, default: nil
44
50
 
45
- # Defines an optional `displayText` for the action.
51
+ # :method: display_text
52
+ # :call-seq:
53
+ # display_text() -> String or nil
54
+ # display_text(value) -> String or nil
55
+ #
56
+ # Sets or gets the display text for the action.
57
+ #
46
58
  # This is the text that will be displayed in the chat as a message from
47
59
  # the user when the action is performed. If not set, no message is displayed.
48
60
  #
49
- # @!method display_text(value = nil)
50
- # @param value [String, nil] The text to display in the chat. Max 300 characters.
51
- # @return [String, nil] The current display text.
61
+ # [value]
62
+ # The text to display in the chat (max 300 characters)
52
63
  option :display_text, default: nil
53
64
 
54
65
  # Initializes a new Postback action.
55
66
  #
56
- # @param data [String] The data to be sent in the postback event. This is required.
57
- # @param context [Object, nil] An optional context object.
58
- # @param options [Hash] Options for the action, including `:label` and `:display_text`.
59
- # @param block [Proc, nil] An optional block for instance_eval.
60
- # @raise [RequiredError] if `data` is nil. (This check is done in `to_h`
61
- # but `data` is conceptually required on initialization).
67
+ # [data]
68
+ # The data to be sent in the postback event (required)
69
+ # [context]
70
+ # An optional context object (default: +nil+)
71
+ # [options]
72
+ # Options for the action, including +:label+ and +:display_text+
73
+ #
74
+ # Raises RequiredError if +data+ is +nil+ (this check is done in +to_h+
75
+ # but +data+ is conceptually required on initialization).
76
+ #
77
+ # == Example
78
+ #
79
+ # postback = Postback.new(
80
+ # "action=buy&item=123",
81
+ # context: view_context,
82
+ # label: "Buy Now",
83
+ # display_text: "I want to buy this item"
84
+ # )
62
85
  def initialize(data, context: nil, **options, &)
63
86
  @data = data
64
87
 
@@ -67,9 +90,16 @@ module Line
67
90
 
68
91
  # Converts the Postback action object to a hash suitable for the LINE Messaging API.
69
92
  #
70
- # @return [Hash] A hash representing the postback action.
71
- # Includes `:type`, `:label` (if set), `:data`, and `:displayText` (if set).
72
- # @raise [RequiredError] if `data` is nil.
93
+ # Returns a hash representing the postback action, including +:type+, +:label+
94
+ # (if set), +:data+, and +:displayText+ (if set).
95
+ #
96
+ # Raises RequiredError if +data+ is +nil+.
97
+ #
98
+ # == Example
99
+ #
100
+ # postback = Postback.new("action=track", label: "Track Order")
101
+ # postback.to_h
102
+ # # => { type: "postback", label: "Track Order", data: "action=track" }
73
103
  def to_h
74
104
  raise RequiredError, "data is required" if data.nil?
75
105
 
@@ -80,7 +110,7 @@ module Line
80
110
 
81
111
  private
82
112
 
83
- def to_api
113
+ def to_api # :nodoc:
84
114
  {
85
115
  type: "postback",
86
116
  label: label,
@@ -89,7 +119,7 @@ module Line
89
119
  }.compact
90
120
  end
91
121
 
92
- def to_sdkv2
122
+ def to_sdkv2 # :nodoc:
93
123
  {
94
124
  type: "postback",
95
125
  label: label,
@@ -0,0 +1,139 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Line
4
+ module Message
5
+ module Builder
6
+ module Actions
7
+ # Represents a URI action for LINE messages.
8
+ #
9
+ # A URI action opens the given URI when the component it is attached to is
10
+ # tapped. Unlike Postback, it needs no webhook handling, which makes it the
11
+ # action to reach for when a component should simply link somewhere.
12
+ #
13
+ # The available schemes are +http+, +https+, +line+ and +tel+.
14
+ #
15
+ # A separate URI can be opened on desktop by setting +alt_uri_desktop+,
16
+ # which maps to the <code>altUri.desktop</code> property. When it is set,
17
+ # LINE for macOS and Windows ignores +uri+. This is supported in Flex
18
+ # Messages but has no effect in a quick reply.
19
+ #
20
+ # == Example: Linking a Flex button to a web page
21
+ #
22
+ # Line::Message::Builder.with do
23
+ # flex alt_text: "Event" do
24
+ # bubble do
25
+ # footer do
26
+ # button style: :primary do
27
+ # uri "https://example.com/event", label: "View event"
28
+ # end
29
+ # end
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ # === Example: Opening a different page on desktop
35
+ #
36
+ # uri "https://example.com/mobile",
37
+ # label: "Open",
38
+ # alt_uri_desktop: "https://example.com/desktop"
39
+ #
40
+ # See also:
41
+ # - https://developers.line.biz/en/reference/messaging-api/#uri-action
42
+ class Uri < Line::Message::Builder::Base
43
+ # The URI opened when the action is performed. This is a required
44
+ # attribute. Max 1000 characters.
45
+ attr_reader :uri
46
+
47
+ # :method: label
48
+ # :call-seq:
49
+ # label() -> String or nil
50
+ # label(value) -> String or nil
51
+ #
52
+ # Sets or gets the label for the action.
53
+ #
54
+ # [value]
55
+ # The label text for the action
56
+ option :label, default: nil
57
+
58
+ # :method: alt_uri_desktop
59
+ # :call-seq:
60
+ # alt_uri_desktop() -> String or nil
61
+ # alt_uri_desktop(value) -> String or nil
62
+ #
63
+ # Sets or gets the URI opened on LINE for macOS and Windows, mapping to
64
+ # the <code>altUri.desktop</code> property. Has no effect in a quick reply.
65
+ #
66
+ # [value]
67
+ # The desktop URI (max 1000 characters)
68
+ option :alt_uri_desktop, default: nil
69
+
70
+ # Initializes a new Uri action.
71
+ #
72
+ # [uri]
73
+ # The URI to open when the action is performed (required)
74
+ # [context]
75
+ # An optional context object (default: +nil+)
76
+ # [options]
77
+ # Options for the action, including +:label+ and +:alt_uri_desktop+
78
+ # [block]
79
+ # An optional block to be instance-eval'd
80
+ #
81
+ # == Example
82
+ #
83
+ # Uri.new("https://example.com", label: "Open")
84
+ def initialize(uri, context: nil, **options, &)
85
+ @uri = uri
86
+
87
+ super(context: context, **options, &)
88
+ end
89
+
90
+ # Converts the Uri action object to a hash suitable for the LINE
91
+ # Messaging API.
92
+ #
93
+ # Raises RequiredError if +uri+ is +nil+.
94
+ #
95
+ # == Example
96
+ #
97
+ # Uri.new("https://example.com", label: "Open").to_h
98
+ # # => { type: "uri", label: "Open", uri: "https://example.com" }
99
+ #
100
+ # [return]
101
+ # A hash representing the URI action
102
+ def to_h
103
+ raise RequiredError, "uri is required" if uri.nil?
104
+
105
+ return to_sdkv2 if context.sdkv2?
106
+
107
+ to_api
108
+ end
109
+
110
+ private
111
+
112
+ def to_api # :nodoc:
113
+ {
114
+ type: "uri",
115
+ label: label,
116
+ uri: uri,
117
+ altUri: alt_uri
118
+ }.compact
119
+ end
120
+
121
+ def to_sdkv2 # :nodoc:
122
+ {
123
+ type: "uri",
124
+ label: label,
125
+ uri: uri,
126
+ alt_uri: alt_uri
127
+ }.compact
128
+ end
129
+
130
+ def alt_uri # :nodoc:
131
+ return if alt_uri_desktop.nil?
132
+
133
+ { desktop: alt_uri_desktop }
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
@@ -18,12 +18,15 @@ module Line
18
18
  # and consistent way to create and manage different types of interactive
19
19
  # elements in messages.
20
20
  #
21
- # @see Line::Message::Builder::Actions::Message
22
- # @see Line::Message::Builder::Actions::Postback
23
- # @see https://developers.line.biz/en/reference/messaging-api/#action-objects
21
+ # See also:
22
+ # - Actions::Message
23
+ # - Actions::Postback
24
+ # - Actions::Uri
25
+ # - https://developers.line.biz/en/reference/messaging-api/#action-objects
24
26
  module Actions
25
27
  require_relative "actions/message"
26
28
  require_relative "actions/postback"
29
+ require_relative "actions/uri"
27
30
  end
28
31
  end
29
32
  end
@@ -3,53 +3,49 @@
3
3
  module Line
4
4
  module Message
5
5
  module Builder
6
- # The `Base` class serves as the foundation for all message builder classes
7
- # within the `Line::Message::Builder` DSL. It provides core functionality
6
+ # The Base class serves as the foundation for all message builder classes
7
+ # within the Line::Message::Builder DSL. It provides core functionality
8
8
  # for defining options, handling initialization, and delegating method calls
9
9
  # to a context object.
10
10
  #
11
11
  # This class is not typically used directly but is inherited by specific
12
- # message type builders (e.g., `Text`, `Flex`).
12
+ # message type builders (e.g., Text, Flex::Builder).
13
13
  class Base
14
14
  class << self
15
- # @!visibility private
16
- # @!parse extend ClassMethods
17
- def inherited(subclass)
15
+ def inherited(subclass) # :nodoc:
18
16
  super
19
17
  subclass.extend ClassMethods
20
18
  end
21
19
  end
22
20
 
23
- # The `ClassMethods` module is automatically extended by any class that
24
- # inherits from {Base}. It provides class-level methods for defining
21
+ # The ClassMethods module is automatically extended by any class that
22
+ # inherits from Base. It provides class-level methods for defining
25
23
  # DSL options and configurations.
26
24
  module ClassMethods
27
- # Returns an array of option names that have been defined for this class
28
- # using the {option} method.
29
- #
30
- # @return [Array<Symbol>] A list of defined option names.
31
- # @!visibility private
32
- def options
25
+ def options # :nodoc:
33
26
  @options ||= []
34
27
  end
35
28
 
36
29
  # Defines a new option for the builder class.
30
+ #
37
31
  # This method dynamically creates an instance method on the builder class
38
- # with the given `name`. When this instance method is called:
32
+ # with the given name. When this instance method is called:
39
33
  # - Without arguments, it returns the current value of the option, or
40
- # the `default` value if not set.
41
- # - With an argument, it sets the value of the option. If a `validator`
34
+ # the default value if not set.
35
+ # - With an argument, it sets the value of the option. If a validator
42
36
  # is provided, the value is validated before being set.
43
37
  #
44
- # @param name [Symbol] The name of the option to define. This will also
45
- # be the name of the generated instance method.
46
- # @param default [Object, nil] The default value for the option if not
47
- # explicitly set.
48
- # @param validator [#valid!?, nil] An optional validator object that must
49
- # respond to `valid!(value)`. If the value is invalid, the validator
50
- # should raise an error (typically a {ValidationError}).
51
- # @return [void]
52
- # @example
38
+ # [name]
39
+ # The name of the option to define. This will also be the name of the
40
+ # generated instance method.
41
+ # [default]
42
+ # The default value for the option if not explicitly set.
43
+ # [validator]
44
+ # An optional validator object that must respond to +valid!+ method.
45
+ # If the value is invalid, the validator should raise an error.
46
+ #
47
+ # == Example
48
+ #
53
49
  # class MyBuilder < Base
54
50
  # option :color, default: "red"
55
51
  # option :size, validator: SizeValidator.new
@@ -74,24 +70,39 @@ module Line
74
70
  end
75
71
  end
76
72
 
73
+ # The context object used for method delegation.
74
+ #
75
+ # When methods are called on the builder that it doesn't respond to,
76
+ # they are delegated to this context object if it responds to them.
77
+ # This allows accessing helper methods and variables from the surrounding
78
+ # environment within the builder DSL.
77
79
  attr_reader :context
78
80
 
79
81
  # Initializes a new instance of a builder class.
80
82
  #
81
- # @param context [Object, nil] An optional external context object.
82
- # Methods not defined in the builder will be delegated to this context
83
- # if it responds to them. This allows for using helper methods or
84
- # accessing data from the surrounding environment within the builder DSL.
85
- # @param options [Hash] A hash of options to set on the builder instance.
86
- # These options are typically defined using the {ClassMethods.option .option}
87
- # method in the builder class.
88
- # @param block [Proc, nil] An optional block that is instance-eval'd
89
- # within the new builder instance. This is the primary way the DSL
90
- # is used to define message content.
83
+ # [context]
84
+ # An optional external context object. Methods not defined in the builder
85
+ # will be delegated to this context if it responds to them. This allows
86
+ # for using helper methods or accessing data from the surrounding environment
87
+ # within the builder DSL.
88
+ # [options]
89
+ # A hash of options to set on the builder instance. These options are
90
+ # typically defined using the +option+ method in the builder class.
91
+ # [block]
92
+ # An optional block that is instance-eval'd within the new builder instance.
93
+ # This is the primary way the DSL is used to define message content.
94
+ #
95
+ # == Example
96
+ #
97
+ # builder = MyBuilder.new(context: view_context) do
98
+ # text "Hello from context"
99
+ # end
91
100
  def initialize(context: nil, **options, &block)
92
101
  @context = context
93
102
  @quick_reply = nil
94
103
 
104
+ validate_options!(options)
105
+
95
106
  self.class.options.each do |option|
96
107
  send(option, options[option]) if options.key?(option)
97
108
  end
@@ -100,15 +111,17 @@ module Line
100
111
  end
101
112
 
102
113
  # Defines a quick reply for the message.
114
+ #
103
115
  # A quick reply consists of a set of buttons that are displayed along
104
116
  # with the message, allowing users to make quick responses.
105
117
  #
106
- # The provided block is executed in the context of a new {QuickReply}
118
+ # The provided block is executed in the context of a new QuickReply
107
119
  # instance, where you can define the quick reply buttons.
108
120
  #
109
- # @yield [quick_reply] The block is yielded with a {QuickReply} instance.
110
- # @return [QuickReply] The created {QuickReply} object.
111
- # @example
121
+ # :yields: quick_reply
122
+ #
123
+ # == Example
124
+ #
112
125
  # text_message do
113
126
  # text "Please choose an option:"
114
127
  # quick_reply do
@@ -120,34 +133,11 @@ module Line
120
133
  @quick_reply = QuickReply.new(context: context, &)
121
134
  end
122
135
 
123
- # Determines if the builder can respond to a given method, including
124
- # checking if the context object can respond to it.
125
- # This is part of Ruby's mechanism for `method_missing` and is used
126
- # here to enable delegation to the `context` object.
127
- #
128
- # @param method_name [Symbol] The name of the method.
129
- # @param include_private [Boolean] Whether to include private methods
130
- # in the search.
131
- # @return [Boolean] `true` if the builder or its context can respond to
132
- # the method, `false` otherwise.
133
- # @!visibility private
134
- def respond_to_missing?(method_name, include_private = false)
136
+ def respond_to_missing?(method_name, include_private = false) # :nodoc:
135
137
  context.respond_to?(method_name, include_private) || super
136
138
  end
137
139
 
138
- # Handles calls to undefined methods by attempting to delegate them to the
139
- # `context` object. If the `context` object responds to the method,
140
- # it is called. Otherwise, it behaves like the standard `method_missing`.
141
- # This allows the DSL to feel more integrated with the surrounding code
142
- # by making methods from the `context` directly available within the
143
- # builder block.
144
- #
145
- # @param method_name [Symbol] The name of the missing method.
146
- # @param ... [Object] Arguments passed to the method.
147
- # @raise [NoMethodError] If neither the builder nor the context can
148
- # handle the method call.
149
- # @!visibility private
150
- def method_missing(method_name, ...)
140
+ def method_missing(method_name, ...) # :nodoc:
151
141
  if context.respond_to?(method_name)
152
142
  context.public_send(method_name, ...)
153
143
  else
@@ -155,6 +145,20 @@ module Line
155
145
  end
156
146
  end
157
147
 
148
+ # Converts the builder to a hash representation.
149
+ #
150
+ # The format of the hash depends on the mode (API or SDK v2) determined
151
+ # by the builder's configuration. Subclasses must implement +to_api+ and
152
+ # +to_sdkv2+ methods to provide the actual conversion logic.
153
+ #
154
+ # [return]
155
+ # A hash representation of the message in the appropriate format.
156
+ #
157
+ # == Example
158
+ #
159
+ # builder = Text.new { text "Hello" }
160
+ # builder.to_h
161
+ # # => { type: "text", text: "Hello" }
158
162
  def to_h
159
163
  return to_sdkv2 if sdkv2?
160
164
 
@@ -163,11 +167,32 @@ module Line
163
167
 
164
168
  private
165
169
 
166
- def to_api
170
+ # Rejects options the builder does not declare.
171
+ #
172
+ # An undeclared option is almost always a typo or a property the LINE API
173
+ # spells differently. Ignoring it produces a message that is silently
174
+ # missing the styling that was asked for, which nothing downstream can
175
+ # detect, so it is refused here instead.
176
+ #
177
+ # [options]
178
+ # The hash of options passed to the constructor
179
+ #
180
+ # Raises ValidationError listing the unknown option and the ones this
181
+ # builder accepts.
182
+ def validate_options!(options) # :nodoc:
183
+ unknown = options.keys - self.class.options
184
+ return if unknown.empty?
185
+
186
+ raise ValidationError,
187
+ "Unknown option: #{unknown.join(", ")} for #{self.class}. " \
188
+ "Allowed options are: #{self.class.options.join(", ")}"
189
+ end
190
+
191
+ def to_api # :nodoc:
167
192
  raise NotImplementedError, "#{self.class} must implement #to_api"
168
193
  end
169
194
 
170
- def to_sdkv2
195
+ def to_sdkv2 # :nodoc:
171
196
  raise NotImplementedError, "#{self.class} must implement #to_sdkv2"
172
197
  end
173
198
  end