ilink 0.1.2

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e4a12ec6f693d67456b1f7cbd06a3bbf7251c15eff1fb963c8b3a6405c756970
4
+ data.tar.gz: '049ddfe910aa9100ee71bd83b61759830b8c48f12f4d1ae2fda49567395b9722'
5
+ SHA512:
6
+ metadata.gz: 0b5eba8c115ef1030ad52b7271e37550b6615b1a643743d00571e278d8fd8826b2f585169e6e29f53721d6064db5f7b9d30f128721d42500a398491281a56454
7
+ data.tar.gz: 549c34e5ec1efcf19ad1baba056f264751ada594bf063be8ff12247aca447ff3dcff10678aee8f6986cf38d6c9c7ee2608153a48b12d41a2a0887d09a1f4dc85
data/README.md ADDED
@@ -0,0 +1,248 @@
1
+ # ILink
2
+
3
+ Ruby SDK for the WeChat iLink Bot API.
4
+
5
+ ## Installation
6
+
7
+ ```ruby
8
+ # Gemfile
9
+ gem "ilink"
10
+ ```
11
+
12
+ Or install directly:
13
+
14
+ ```sh
15
+ gem install ilink
16
+ ```
17
+
18
+ ## Quick Start
19
+
20
+ ```ruby
21
+ require "ilink"
22
+
23
+ ILink.configure do |c|
24
+ c.token = "your_bot_token"
25
+ end
26
+
27
+ bot = ILink::Bot.new
28
+ ```
29
+
30
+ ## Configuration
31
+
32
+ | Option | Default | Description |
33
+ | ------------------- | ------------------------------- | --------------------------------------------------------------- |
34
+ | `base_url` | `https://ilinkai.weixin.qq.com` | API base URL |
35
+ | `token` | `nil` | Bot token (Bearer auth) |
36
+ | `app_id` | `"bot"` | iLink-App-Id header |
37
+ | `app_version` | `"2.1.1"` | Client version string |
38
+ | `timeout` | `15` | Default request timeout (seconds) |
39
+ | `long_poll_timeout` | `35` | Long-poll timeout for `get_updates` / `qrcode_status` (seconds) |
40
+ | `route_tag` | `nil` | Optional SKRouteTag header |
41
+
42
+ Global configuration applies to all `Bot` instances. You can also override per-instance:
43
+
44
+ ```ruby
45
+ bot = ILink::Bot.new(
46
+ token: "another_token",
47
+ base_url: "https://custom-host.example.com"
48
+ )
49
+ ```
50
+
51
+ ## API Reference
52
+
53
+ ### QR Code Login
54
+
55
+ ```ruby
56
+ bot = ILink::Bot.new
57
+
58
+ # Step 1: Get a QR code
59
+ qr = bot.qrcode
60
+ puts qr[:qrcode_img_content] # QR code image URL — show to user
61
+
62
+ # Step 2: Poll scan status (long-poll, blocks until scanned or timeout)
63
+ loop do
64
+ status = bot.qrcode_status(qrcode: qr[:qrcode])
65
+ case status[:status]
66
+ when "confirmed"
67
+ puts "Login success!"
68
+ puts "bot_token: #{status[:bot_token]}"
69
+ puts "ilink_bot_id: #{status[:ilink_bot_id]}"
70
+ puts "base_url: #{status[:baseurl]}"
71
+ break
72
+ when "scaned"
73
+ puts "Scanned, waiting for confirmation..."
74
+ when "expired"
75
+ puts "QR code expired, request a new one"
76
+ break
77
+ when "scaned_but_redirect"
78
+ # IDC redirect — switch polling host
79
+ puts "Redirecting to #{status[:redirect_host]}"
80
+ end
81
+ end
82
+ ```
83
+
84
+ ### Polling Messages
85
+
86
+ ```ruby
87
+ bot = ILink::Bot.new(token: "your_bot_token")
88
+
89
+ buf = ""
90
+ loop do
91
+ resp = bot.get_updates(buf: buf)
92
+ buf = resp[:get_updates_buf] || buf
93
+
94
+ (resp[:msgs] || []).each do |msg|
95
+ from = msg[:from_user_id]
96
+ msg[:item_list]&.each do |item|
97
+ case item[:type]
98
+ when ILink::MessageItemType::TEXT
99
+ puts "#{from}: #{item[:text_item][:text]}"
100
+ when ILink::MessageItemType::IMAGE
101
+ puts "#{from}: [image] #{item[:image_item][:url]}"
102
+ end
103
+ end
104
+ end
105
+ end
106
+ ```
107
+
108
+ ### Sending Messages
109
+
110
+ ```ruby
111
+ # Send a simple text message
112
+ bot.send_text(to: "user_id", text: "Hello from Ruby!")
113
+
114
+ # Send with session ID
115
+ bot.send_text(to: "user_id", text: "Reply in session", session_id: "session_123")
116
+
117
+ # Send a custom message (image, file, video, etc.)
118
+ bot.send_message({
119
+ to_user_id: "user_id",
120
+ message_type: ILink::MessageType::BOT,
121
+ message_state: ILink::MessageState::FINISH,
122
+ item_list: [
123
+ {
124
+ type: ILink::MessageItemType::IMAGE,
125
+ image_item: {
126
+ media: { encrypt_query_param: "...", aes_key: "..." }
127
+ }
128
+ }
129
+ ]
130
+ })
131
+ ```
132
+
133
+ ### Media Upload
134
+
135
+ ```ruby
136
+ resp = bot.upload_url(
137
+ media_type: ILink::UploadMediaType::IMAGE,
138
+ to_user_id: "user_id",
139
+ rawsize: File.size("photo.jpg"),
140
+ rawfilemd5: Digest::MD5.hexdigest(File.read("photo.jpg")),
141
+ filesize: encrypted_size,
142
+ aeskey: aes_key_hex
143
+ )
144
+
145
+ puts resp[:upload_full_url] # Pre-signed upload URL
146
+ puts resp[:upload_param] # Upload encryption param
147
+ puts resp[:thumb_upload_param] # Thumbnail upload param (if applicable)
148
+ ```
149
+
150
+ ### Typing Indicators
151
+
152
+ ```ruby
153
+ # Get the typing ticket first
154
+ config = bot.get_config(user_id: "user_id")
155
+ ticket = config[:typing_ticket]
156
+
157
+ # Show "typing..."
158
+ bot.send_typing(user_id: "user_id", ticket: ticket)
159
+
160
+ # Cancel typing
161
+ bot.cancel_typing(user_id: "user_id", ticket: ticket)
162
+ ```
163
+
164
+ ### Bot Config
165
+
166
+ ```ruby
167
+ config = bot.get_config(user_id: "user_id", context_token: "optional_token")
168
+ puts config[:typing_ticket]
169
+ ```
170
+
171
+ ## Constants
172
+
173
+ ```ruby
174
+ ILink::UploadMediaType::IMAGE # 1
175
+ ILink::UploadMediaType::VIDEO # 2
176
+ ILink::UploadMediaType::FILE # 3
177
+ ILink::UploadMediaType::VOICE # 4
178
+
179
+ ILink::MessageType::USER # 1
180
+ ILink::MessageType::BOT # 2
181
+
182
+ ILink::MessageItemType::TEXT # 1
183
+ ILink::MessageItemType::IMAGE # 2
184
+ ILink::MessageItemType::VOICE # 3
185
+ ILink::MessageItemType::FILE # 4
186
+ ILink::MessageItemType::VIDEO # 5
187
+
188
+ ILink::MessageState::NEW # 0
189
+ ILink::MessageState::GENERATING # 1
190
+ ILink::MessageState::FINISH # 2
191
+
192
+ ILink::TypingStatus::TYPING # 1
193
+ ILink::TypingStatus::CANCEL # 2
194
+ ```
195
+
196
+ ## Error Handling
197
+
198
+ ```ruby
199
+ begin
200
+ bot.send_text(to: "user_id", text: "hi")
201
+ rescue ILink::AuthenticationError => e
202
+ puts "Auth failed (#{e.status}): #{e.body}"
203
+ rescue ILink::ApiError => e
204
+ puts "API error (#{e.status}): #{e.body}"
205
+ rescue Net::ReadTimeout
206
+ puts "Request timed out"
207
+ end
208
+ ```
209
+
210
+ ## Full Echo Bot Example
211
+
212
+ ```ruby
213
+ require "ilink"
214
+
215
+ ILink.configure do |c|
216
+ c.token = ENV["ILINK_BOT_TOKEN"]
217
+ end
218
+
219
+ bot = ILink::Bot.new
220
+ buf = ""
221
+
222
+ puts "Bot started, waiting for messages..."
223
+
224
+ loop do
225
+ resp = bot.get_updates(get_updates_buf: buf)
226
+ buf = resp[:get_updates_buf] || buf
227
+
228
+ (resp[:msgs] || []).each do |msg|
229
+ next unless msg[:message_type] == ILink::MessageType::USER
230
+
231
+ from = msg[:from_user_id]
232
+ msg[:item_list]&.each do |item|
233
+ next unless item[:type] == ILink::MessageItemType::TEXT
234
+
235
+ text = item.dig(:text_item, :text)
236
+ puts "Received: #{text} from #{from}"
237
+ bot.send_text(to: from, text: "Echo: #{text}", session_id: msg[:session_id])
238
+ end
239
+ end
240
+ rescue ILink::ApiError => e
241
+ warn "API error: #{e.message}"
242
+ sleep 3
243
+ end
244
+ ```
245
+
246
+ ## License
247
+
248
+ MIT
data/lib/ilink/bot.rb ADDED
@@ -0,0 +1,135 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ # Main entry point for the iLink Bot API.
5
+ #
6
+ # bot = ILink::Bot.new(token: "your_bot_token")
7
+ # bot.get_updates
8
+ # bot.send_text(to: "user_id", text: "Hello!")
9
+ # bot.upload_url(media_type: 1, to_user_id: "user_id", ...)
10
+ # bot.send_typing(user_id: "...", ticket: "...")
11
+ # bot.get_config(user_id: "...")
12
+ # bot.create_qr_code
13
+ #
14
+ class Bot
15
+ attr_reader :configuration
16
+
17
+ def initialize(token: nil, base_url: nil, **options)
18
+ @configuration = ILink.configuration.dup
19
+ @configuration.token = token if token
20
+ @configuration.base_url = base_url if base_url
21
+
22
+ options.each do |key, value|
23
+ @configuration.public_send(:"#{key}=", value) if @configuration.respond_to?(:"#{key}=")
24
+ end
25
+ end
26
+
27
+ # Long-poll for new messages.
28
+ #
29
+ # @param buf [String] opaque cursor from previous call ("" on first call)
30
+ # @return [Hash] parsed response with :ret, :msgs, :get_updates_buf, etc.
31
+ def get_updates(buf: "")
32
+ connection.post("/ilink/bot/getupdates",
33
+ { get_updates_buf: buf },
34
+ timeout: @configuration.long_poll_timeout)
35
+ rescue Net::ReadTimeout, Net::OpenTimeout
36
+ { ret: 0, msgs: [], get_updates_buf: buf }
37
+ end
38
+
39
+ # Send a message.
40
+ #
41
+ # @param message [Hash] a WeixinMessage hash with keys like :to_user_id, :item_list, etc.
42
+ # @return [Hash] parsed response
43
+ def send_message(message)
44
+ connection.post("/ilink/bot/sendmessage", { msg: message })
45
+ end
46
+
47
+ # Convenience: send a text message to a user.
48
+ #
49
+ # @param to [String] target user ID
50
+ # @param text [String] message text
51
+ # @param session_id [String, nil] optional session ID
52
+ # @return [Hash]
53
+ def send_text(to:, text:, session_id: nil)
54
+ message = {
55
+ to_user_id: to,
56
+ message_type: MessageType::BOT,
57
+ message_state: MessageState::FINISH,
58
+ item_list: [
59
+ { type: MessageItemType::TEXT, text_item: { text: text } }
60
+ ]
61
+ }
62
+ message[:session_id] = session_id if session_id
63
+ send_message(message)
64
+ end
65
+
66
+ # Get a pre-signed CDN upload URL.
67
+ #
68
+ # @param params [Hash] :filekey, :media_type, :to_user_id, :rawsize, :rawfilemd5,
69
+ # :filesize, :aeskey, :thumb_rawsize, :thumb_rawfilemd5, :thumb_filesize, :no_need_thumb
70
+ # @return [Hash] { upload_param:, thumb_upload_param:, upload_full_url: }
71
+ def upload_url(**params)
72
+ connection.post("/ilink/bot/getuploadurl", params)
73
+ end
74
+
75
+ # Send a typing indicator.
76
+ #
77
+ # @param user_id [String] ilink user ID
78
+ # @param ticket [String] typing ticket (from get_config)
79
+ # @return [Hash]
80
+ def send_typing(user_id:, ticket:)
81
+ set_typing(user_id: user_id, ticket: ticket, status: TypingStatus::TYPING)
82
+ end
83
+
84
+ # Cancel a typing indicator.
85
+ #
86
+ # @param user_id [String] ilink user ID
87
+ # @param ticket [String] typing ticket
88
+ # @return [Hash]
89
+ def cancel_typing(user_id:, ticket:)
90
+ set_typing(user_id: user_id, ticket: ticket, status: TypingStatus::CANCEL)
91
+ end
92
+
93
+ def set_typing(user_id:, ticket:, status:)
94
+ connection.post("/ilink/bot/sendtyping", {
95
+ ilink_user_id: user_id,
96
+ typing_ticket: ticket,
97
+ status: status
98
+ }, timeout: 10)
99
+ end
100
+
101
+ # Fetch bot config for a user (includes typing_ticket).
102
+ #
103
+ # @param user_id [String] ilink user ID
104
+ # @param context_token [String, nil] optional context token
105
+ # @return [Hash] { ret:, typing_ticket:, ... }
106
+ def get_config(user_id:, context_token: nil)
107
+ body = { ilink_user_id: user_id, context_token: context_token }.compact
108
+ connection.post("/ilink/bot/getconfig", body, timeout: 10)
109
+ end
110
+
111
+ # Request a new QR code for login.
112
+ #
113
+ # @param bot_type [String] bot type identifier (default "3")
114
+ # @return [Hash] { qrcode:, qrcode_img_content: }
115
+ def qrcode(bot_type: "3")
116
+ connection.get("/ilink/bot/get_bot_qrcode?bot_type=#{URI.encode_www_form_component(bot_type)}", timeout: 5)
117
+ end
118
+
119
+ # Poll QR code scan status (long-poll).
120
+ #
121
+ # @param qrcode [String] qrcode value from #create_qr_code
122
+ # @return [Hash] { status:, bot_token:, ilink_bot_id:, baseurl:, ilink_user_id:, redirect_host: }
123
+ def qrcode_status(qrcode:)
124
+ connection.get("/ilink/bot/get_qrcode_status?qrcode=#{URI.encode_www_form_component(qrcode)}",
125
+ timeout: @configuration.long_poll_timeout)
126
+ rescue Net::ReadTimeout, Net::OpenTimeout
127
+ { status: "wait" }
128
+ end
129
+
130
+ private
131
+ def connection
132
+ Connection.new(@configuration)
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ class Configuration
5
+ attr_accessor :base_url, :token
6
+ attr_accessor :app_id, :app_version
7
+ attr_accessor :timeout, :long_poll_timeout, :route_tag
8
+
9
+ DEFAULT_BASE_URL = "https://ilinkai.weixin.qq.com"
10
+ DEFAULT_APP_ID = "bot"
11
+ DEFAULT_APP_VERSION = "2.1.1"
12
+ DEFAULT_TIMEOUT = 15
13
+ DEFAULT_LONG_POLL_TIMEOUT = 35
14
+
15
+ def initialize
16
+ @base_url = DEFAULT_BASE_URL
17
+ @app_id = DEFAULT_APP_ID
18
+ @app_version = DEFAULT_APP_VERSION
19
+ @timeout = DEFAULT_TIMEOUT
20
+ @long_poll_timeout = DEFAULT_LONG_POLL_TIMEOUT
21
+ @token = nil
22
+ @route_tag = nil
23
+ end
24
+
25
+ # Encode version string "M.N.P" as uint32: 0x00MMNNPP
26
+ def client_version_int
27
+ parts = (app_version || "0.0.0").split(".").map(&:to_i)
28
+ ((parts[0] & 0xFF) << 16) | ((parts[1].to_i & 0xFF) << 8) | (parts[2].to_i & 0xFF)
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require "json"
5
+ require "base64"
6
+ require "securerandom"
7
+ require "net/http"
8
+
9
+ module ILink
10
+ # Low-level HTTP connection using Ruby's built-in net/http.
11
+ # Builds proper iLink headers, handles timeouts, and parses JSON responses.
12
+ class Connection
13
+ attr_reader :config
14
+
15
+ def initialize(config)
16
+ @config = config
17
+ end
18
+
19
+ def get(endpoint, timeout: config.timeout, headers: {})
20
+ uri = build_uri(endpoint)
21
+ request = Net::HTTP::Get.new(uri)
22
+ common_headers.merge(headers).each { |k, v| request[k] = v }
23
+
24
+ execute(uri, request, timeout: timeout)
25
+ end
26
+
27
+ def post(endpoint, body = {}, timeout: config.timeout, headers: {})
28
+ uri = build_uri(endpoint)
29
+ payload = body.merge(base_info: { channel_version: config.app_version })
30
+ json_body = JSON.generate(payload)
31
+
32
+ request = Net::HTTP::Post.new(uri)
33
+ post_headers.merge(headers).each { |k, v| request[k] = v }
34
+ request.body = json_body
35
+
36
+ execute(uri, request, timeout: timeout)
37
+ end
38
+
39
+ private
40
+ def build_uri(endpoint)
41
+ URI.join(config.base_url.to_s, endpoint)
42
+ end
43
+
44
+ def common_headers
45
+ headers = {
46
+ "iLink-App-Id" => config.app_id,
47
+ "iLink-App-ClientVersion" => config.client_version_int.to_s
48
+ }
49
+ headers["SKRouteTag"] = config.route_tag if config.route_tag
50
+ headers
51
+ end
52
+
53
+ def post_headers
54
+ headers = common_headers.merge(
55
+ "Content-Type" => "application/json",
56
+ "AuthorizationType" => "ilink_bot_token",
57
+ "X-WECHAT-UIN" => random_wechat_uin
58
+ )
59
+ headers["Authorization"] = "Bearer #{config.token}" if config.token
60
+ headers
61
+ end
62
+
63
+ def random_wechat_uin
64
+ uint32 = SecureRandom.random_number(2**32)
65
+ Base64.strict_encode64(uint32.to_s)
66
+ end
67
+
68
+ def execute(uri, request, timeout:)
69
+ http = Net::HTTP.new(uri.host, uri.port)
70
+ http.use_ssl = (uri.scheme == "https")
71
+ http.open_timeout = [timeout, 10].min
72
+ http.read_timeout = timeout
73
+ http.write_timeout = timeout
74
+
75
+ response = http.request(request)
76
+ handle_response(response)
77
+ end
78
+
79
+ def handle_response(response)
80
+ case response.code.to_i
81
+ when 200..299
82
+ body = response.body.to_s
83
+ body.empty? ? {} : JSON.parse(body, symbolize_names: true)
84
+ when 401, 403
85
+ raise AuthenticationError.new(response.code.to_i, response.body)
86
+ else
87
+ raise ApiError.new(response.code.to_i, response.body)
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ # Media type for upload requests
5
+ module UploadMediaType
6
+ IMAGE = 1
7
+ VIDEO = 2
8
+ FILE = 3
9
+ VOICE = 4
10
+ end
11
+
12
+ # Message sender type
13
+ module MessageType
14
+ NONE = 0
15
+ USER = 1
16
+ BOT = 2
17
+ end
18
+
19
+ # Message content type
20
+ module MessageItemType
21
+ NONE = 0
22
+ TEXT = 1
23
+ IMAGE = 2
24
+ VOICE = 3
25
+ FILE = 4
26
+ VIDEO = 5
27
+ end
28
+
29
+ # Message lifecycle state
30
+ module MessageState
31
+ NEW = 0
32
+ GENERATING = 1
33
+ FINISH = 2
34
+ end
35
+
36
+ # Typing indicator status
37
+ module TypingStatus
38
+ TYPING = 1
39
+ CANCEL = 2
40
+ end
41
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ class Error < StandardError; end
5
+
6
+ # Raised on non-2xx HTTP responses
7
+ class ApiError < Error
8
+ attr_reader :status, :body
9
+
10
+ def initialize(status, body)
11
+ @status = status
12
+ @body = body
13
+ super("iLink API error #{status}: #{body}")
14
+ end
15
+ end
16
+
17
+ # Raised when long-poll times out.
18
+ class TimeoutError < Error; end
19
+
20
+ # Raised on authentication failures (401/403)
21
+ class AuthenticationError < ApiError; end
22
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ module Resources
5
+ # RESTful resource for message operations.
6
+ #
7
+ # bot.messages.get_updates - long-poll for new messages (getUpdates)
8
+ # bot.messages.send(msg) - send a message to a user
9
+ class Messages < Base
10
+ # Long-poll for new messages.
11
+ #
12
+ # @param get_updates_buf [String] opaque cursor from previous poll ("" on first call)
13
+ # @return [Hash] parsed response with :ret, :msgs, :get_updates_buf, etc.
14
+ def poll(get_updates_buf: "")
15
+ post("/ilink/bot/getupdates",
16
+ { get_updates_buf: get_updates_buf },
17
+ timeout: connection.config.long_poll_timeout)
18
+ rescue Net::ReadTimeout, Net::OpenTimeout
19
+ # Long-poll timeout is normal; return empty response so caller can retry
20
+ { ret: 0, msgs: [], get_updates_buf: get_updates_buf }
21
+ end
22
+
23
+ # Send a message.
24
+ #
25
+ # @param message [Hash] a WeixinMessage hash with keys like :to_user_id, :item_list, etc.
26
+ # @return [Hash] parsed response
27
+ def send(message)
28
+ post("/ilink/bot/sendmessage", { msg: message })
29
+ end
30
+
31
+ # Convenience: send a text message to a user.
32
+ #
33
+ # @param to [String] target user ID
34
+ # @param text [String] message text
35
+ # @param session_id [String, nil] optional session ID
36
+ # @return [Hash]
37
+ def send_text(to:, text:, session_id: nil)
38
+ message = {
39
+ to_user_id: to,
40
+ message_type: MessageType::BOT,
41
+ message_state: MessageState::FINISH,
42
+ item_list: [
43
+ {
44
+ type: MessageItemType::TEXT,
45
+ text_item: { text: text }
46
+ }
47
+ ]
48
+ }
49
+ message[:session_id] = session_id if session_id
50
+ send(message)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ILink
4
+ VERSION = "0.1.2"
5
+ end
data/lib/ilink.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "ilink/version"
4
+ require_relative "ilink/errors"
5
+ require_relative "ilink/constants"
6
+ require_relative "ilink/configuration"
7
+ require_relative "ilink/connection"
8
+ require_relative "ilink/bot"
9
+
10
+ module ILink
11
+ class << self
12
+ def configuration
13
+ @configuration ||= Configuration.new
14
+ end
15
+
16
+ def configure
17
+ yield(configuration)
18
+ end
19
+
20
+ def reset_configuration!
21
+ @configuration = Configuration.new
22
+ end
23
+ end
24
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ilink
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.2
5
+ platform: ruby
6
+ authors:
7
+ - Songji Zeng
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: |
27
+ A lightweight Ruby SDK for building WeChat bots via the iLink Bot API.
28
+ Handles QR login, long-polling for messages, sending replies, and typing indicators.
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - README.md
34
+ - lib/ilink.rb
35
+ - lib/ilink/bot.rb
36
+ - lib/ilink/configuration.rb
37
+ - lib/ilink/connection.rb
38
+ - lib/ilink/constants.rb
39
+ - lib/ilink/errors.rb
40
+ - lib/ilink/resources/messages.rb
41
+ - lib/ilink/version.rb
42
+ licenses:
43
+ - MIT
44
+ metadata: {}
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '3.1'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubygems_version: 4.0.9
60
+ specification_version: 4
61
+ summary: Ruby SDK for the WeChat iLink Bot API
62
+ test_files: []