luosimao-sms 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 4b04f880e4d7bb95c53ca54950ddcb29a2c7e7780a8bcc1674d379812d957c59
4
+ data.tar.gz: d32d0083600b86cdeef688b06ff7d345f9928ed95fd64cf809dff474c4367091
5
+ SHA512:
6
+ metadata.gz: dcf71f6970bda2685bc21baf2dfd4955fdf1f1ef33a28dd1587a16ca08760e1c13c7035493701aae9034802bd7efad1b524ed3fff9e33f36d45431862f5a31e6
7
+ data.tar.gz: 2181349a2b92bc23e455b8e17548916903187ce7fc04e2fd33f0ca318cf322e91b721c891bc9b9ddaf54c5c5b49d129507a45c8b25cdad2ffdf247cf92887bdb
data/CHANGELOG.md ADDED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-03-29
6
+ ### Added
7
+ - Initial release of `luosimao-sms` gem.
8
+ - Support for `send`, `send_batch`, and `status` API endpoints.
9
+ - Error handling and response parsing.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 luosimao-oss
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # Luosimao SMS Ruby SDK
2
+
3
+ [English](#english) | [中文](#chinese)
4
+
5
+ <a id="english"></a>
6
+ ## English
7
+
8
+ A Ruby SDK for sending SMS via the [Luosimao](https://luosimao.com) API.
9
+
10
+ ### Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ ```ruby
15
+ gem 'luosimao-sms'
16
+ ```
17
+
18
+ And then execute:
19
+
20
+ ```bash
21
+ $ bundle install
22
+ ```
23
+
24
+ Or install it yourself as:
25
+
26
+ ```bash
27
+ $ gem install luosimao-sms
28
+ ```
29
+
30
+ ### Quick Start
31
+
32
+ ```ruby
33
+ require 'luosimao-sms'
34
+
35
+ client = Luosimao::SMS::Client.new(api_key: 'your_api_key')
36
+
37
+ # Send SMS
38
+ response = client.send(mobile: '13800138000', message: 'Verification code 123456【Your Company】')
39
+ puts response.success?
40
+
41
+ # Batch Send SMS
42
+ response = client.send_batch(
43
+ mobiles: ['13800138000', '13800138001'],
44
+ message: 'Batch message【Your Company】'
45
+ )
46
+
47
+ # Check Balance
48
+ status = client.status
49
+ puts status.deposit
50
+ ```
51
+
52
+ ### Configuration
53
+
54
+ You can configure the client with optional parameters:
55
+
56
+ ```ruby
57
+ client = Luosimao::SMS::Client.new(
58
+ api_key: 'your_api_key',
59
+ timeout: 10, # Read timeout in seconds (default: 30)
60
+ open_timeout: 5, # Connection timeout in seconds (default: 10)
61
+ base_url: 'https://sms-api.luosimao.com' # API base URL
62
+ )
63
+ ```
64
+
65
+ The `api_key` can be provided with or without the `key-` prefix.
66
+
67
+ ### API Reference
68
+
69
+ #### `client.send(mobile:, message:)`
70
+ Sends a single SMS message. Returns `Luosimao::SMS::Response`. Raises `Luosimao::SMS::APIError` on API failure.
71
+
72
+ #### `client.send_batch(mobiles:, message:, send_at: nil)`
73
+ Sends a batch SMS message. Returns `Luosimao::SMS::Response`.
74
+
75
+ #### `client.status`
76
+ Queries the account balance. Returns `Luosimao::SMS::StatusResponse`.
77
+
78
+ ### Error Handling
79
+
80
+ The SDK raises errors on failure:
81
+ - `Luosimao::SMS::APIError`: API returned an error code. You can check specific errors via methods like `auth_failed?`, `insufficient_balance?`, `sensitive_words?`, `ip_not_allowed?`.
82
+ - `Luosimao::SMS::NetworkError`: Network-related errors (timeout, connection refused).
83
+ - `Luosimao::SMS::ArgumentError`: Invalid arguments provided.
84
+
85
+ ### Development
86
+
87
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `bundle exec rspec` to run the tests.
88
+
89
+ ### Contributing
90
+
91
+ Bug reports and pull requests are welcome on GitHub at https://github.com/luosimao-oss/luosimao-sms-ruby.
92
+
93
+ ### License
94
+
95
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
96
+
97
+ ---
98
+
99
+ <a id="chinese"></a>
100
+ ## 中文
101
+
102
+ 螺丝帽 (Luosimao) 短信服务的 Ruby SDK。
103
+
104
+ ### Installation(安装)
105
+
106
+ 将以下代码添加到你的 `Gemfile`:
107
+
108
+ ```ruby
109
+ gem 'luosimao-sms'
110
+ ```
111
+
112
+ 然后执行:
113
+
114
+ ```bash
115
+ $ bundle install
116
+ ```
117
+
118
+ 或者直接安装:
119
+
120
+ ```bash
121
+ $ gem install luosimao-sms
122
+ ```
123
+
124
+ ### Quick Start(快速开始)
125
+
126
+ ```ruby
127
+ require 'luosimao-sms'
128
+
129
+ client = Luosimao::SMS::Client.new(api_key: 'your_api_key')
130
+
131
+ # 单发短信
132
+ response = client.send(mobile: '13800138000', message: '验证码123456【你的公司】')
133
+ puts response.success?
134
+
135
+ # 批量发送
136
+ response = client.send_batch(
137
+ mobiles: ['13800138000', '13800138001'],
138
+ message: '活动通知【你的公司】'
139
+ )
140
+
141
+ # 查询余额
142
+ status = client.status
143
+ puts status.deposit
144
+ ```
145
+
146
+ ### Configuration(配置项说明)
147
+
148
+ 支持以下可选配置参数:
149
+
150
+ ```ruby
151
+ client = Luosimao::SMS::Client.new(
152
+ api_key: 'your_api_key',
153
+ timeout: 10, # 超时秒数,默认 30
154
+ open_timeout: 5, # 连接超时,默认 10
155
+ base_url: 'https://sms-api.luosimao.com' # 基础 URL
156
+ )
157
+ ```
158
+
159
+ `api_key` 传入时会自动补全 `key-` 前缀(如果未带)。
160
+
161
+ ### API Reference(接口说明)
162
+
163
+ #### `client.send(mobile:, message:)`
164
+ 单发短信。返回 `Luosimao::SMS::Response` 对象。
165
+
166
+ #### `client.send_batch(mobiles:, message:, send_at: nil)`
167
+ 批量发送。返回 `Luosimao::SMS::Response` 对象。
168
+
169
+ #### `client.status`
170
+ 查询账户余额。返回 `Luosimao::SMS::StatusResponse` 对象。
171
+
172
+ ### Error Handling(异常处理)
173
+
174
+ SDK 在遇到错误时会抛出异常:
175
+ - `Luosimao::SMS::APIError`: 接口返回错误。支持使用 `auth_failed?`, `insufficient_balance?`, `sensitive_words?`, `ip_not_allowed?` 等方法检查具体错误。
176
+ - `Luosimao::SMS::NetworkError`: 网络请求异常(超时、连接失败等)。
177
+ - `Luosimao::SMS::ArgumentError`: 参数错误。
178
+
179
+ ### Development(本地开发)
180
+
181
+ 检出代码后,运行 `bundle install` 安装依赖。然后运行 `bundle exec rspec` 执行测试用例。
182
+
183
+ ### Contributing(贡献指南)
184
+
185
+ 欢迎提交 Issue 和 Pull Request:https://github.com/luosimao-oss/luosimao-sms-ruby。
186
+
187
+ ### License
188
+
189
+ 遵循 [MIT License](https://opensource.org/licenses/MIT) 开源协议。
@@ -0,0 +1,122 @@
1
+ module Luosimao
2
+ module SMS
3
+ class Client
4
+ attr_reader :api_key, :timeout, :open_timeout, :base_url
5
+
6
+ def initialize(api_key:, timeout: 30, open_timeout: 10, base_url: "https://sms-api.luosimao.com")
7
+ @api_key = api_key.to_s.start_with?("key-") ? api_key.to_s : "key-#{api_key}"
8
+ @timeout = timeout
9
+ @open_timeout = open_timeout
10
+ @base_url = base_url.chomp("/")
11
+ end
12
+
13
+ # 单发短信
14
+ # @param mobile [String] 手机号
15
+ # @param message [String] 短信内容(含签名)
16
+ # @return [Luosimao::SMS::Response]
17
+ # @raise [Luosimao::SMS::APIError, Luosimao::SMS::NetworkError, Luosimao::SMS::ArgumentError]
18
+ def send(mobile:, message:)
19
+ raise Luosimao::SMS::ArgumentError, "mobile is required" if mobile.nil? || mobile.empty?
20
+ raise Luosimao::SMS::ArgumentError, "message is required" if message.nil? || message.empty?
21
+
22
+ response = request(
23
+ method: :post,
24
+ path: "/v1/send.json",
25
+ form_data: {
26
+ "mobile" => mobile,
27
+ "message" => message
28
+ }
29
+ )
30
+
31
+ handle_response(response, Response)
32
+ end
33
+
34
+ # 批量发送
35
+ # @param mobiles [Array<String>] 手机号数组
36
+ # @param message [String] 短信内容
37
+ # @param send_at [Time, nil] 定时发送时间(可选)
38
+ # @return [Luosimao::SMS::Response]
39
+ def send_batch(mobiles:, message:, send_at: nil)
40
+ raise Luosimao::SMS::ArgumentError, "mobiles array must be a non-empty array" unless mobiles.is_a?(Array) && !mobiles.empty?
41
+ raise Luosimao::SMS::ArgumentError, "message is required" if message.nil? || message.empty?
42
+
43
+ form_data = {
44
+ "mobile_list" => mobiles.join(","),
45
+ "message" => message
46
+ }
47
+ form_data["time"] = send_at.strftime("%Y-%m-%d %H:%M:%S") if send_at
48
+
49
+ response = request(
50
+ method: :post,
51
+ path: "/v1/send_batch.json",
52
+ form_data: form_data
53
+ )
54
+
55
+ handle_response(response, Response)
56
+ end
57
+
58
+ # 查询余额
59
+ # @return [Luosimao::SMS::StatusResponse]
60
+ def status
61
+ response = request(
62
+ method: :get,
63
+ path: "/v1/status.json"
64
+ )
65
+
66
+ handle_response(response, StatusResponse)
67
+ end
68
+
69
+ private
70
+
71
+ def request(method:, path:, form_data: nil)
72
+ uri = URI.parse("#{@base_url}#{path}")
73
+ http = Net::HTTP.new(uri.host, uri.port)
74
+ http.use_ssl = (uri.scheme == "https")
75
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER if http.use_ssl?
76
+ http.read_timeout = @timeout
77
+ http.open_timeout = @open_timeout
78
+
79
+ req = case method
80
+ when :get
81
+ Net::HTTP::Get.new(uri.request_uri)
82
+ when :post
83
+ Net::HTTP::Post.new(uri.request_uri)
84
+ else
85
+ raise Luosimao::SMS::ArgumentError, "unsupported method \#{method}"
86
+ end
87
+
88
+ req.basic_auth("api", @api_key)
89
+
90
+ if method == :post && form_data
91
+ req.set_form_data(form_data)
92
+ end
93
+
94
+ begin
95
+ http.request(req)
96
+ rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, SocketError => e
97
+ raise Luosimao::SMS::NetworkError, e.message
98
+ end
99
+ end
100
+
101
+ def handle_response(http_response, response_class)
102
+ unless http_response.code.to_i.between?(200, 299)
103
+ raise Luosimao::SMS::NetworkError, "HTTP #{http_response.code}: #{http_response.message}"
104
+ end
105
+
106
+ begin
107
+ body = JSON.parse(http_response.body)
108
+ rescue JSON::ParserError => e
109
+ body_preview = http_response.body ? http_response.body[0..200] : ""
110
+ raise Luosimao::SMS::NetworkError, "Invalid JSON response: #{e.message}. Body: #{body_preview}"
111
+ end
112
+
113
+ resp = response_class.new(body, http_response.code.to_i)
114
+ if resp.success?
115
+ resp
116
+ else
117
+ raise Luosimao::SMS::APIError.new(code: resp.error_code, message: resp.message)
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,38 @@
1
+ module Luosimao
2
+ module SMS
3
+ # 基础异常
4
+ class Error < StandardError; end
5
+
6
+ # API 返回错误
7
+ class APIError < Error
8
+ attr_reader :code
9
+
10
+ def initialize(code:, message:)
11
+ @code = code.to_i
12
+ super(message)
13
+ end
14
+
15
+ def auth_failed?
16
+ @code == -10
17
+ end
18
+
19
+ def insufficient_balance?
20
+ @code == -20
21
+ end
22
+
23
+ def sensitive_words?
24
+ @code == -30 || @code == -31
25
+ end
26
+
27
+ def ip_not_allowed?
28
+ @code == -50
29
+ end
30
+ end
31
+
32
+ # 网络请求异常
33
+ class NetworkError < Error; end
34
+
35
+ # 参数校验异常
36
+ class ArgumentError < Error; end
37
+ end
38
+ end
@@ -0,0 +1,34 @@
1
+ module Luosimao
2
+ module SMS
3
+ class Response
4
+ attr_reader :raw, :http_code
5
+
6
+ def initialize(raw, http_code = 200)
7
+ @raw = raw || {}
8
+ @http_code = http_code
9
+ end
10
+
11
+ def success?
12
+ error_code == 0
13
+ end
14
+
15
+ def error_code
16
+ @raw["error"].to_i
17
+ end
18
+
19
+ def message
20
+ @raw["msg"] || ""
21
+ end
22
+ end
23
+
24
+ class StatusResponse < Response
25
+ def deposit
26
+ if @raw["result"] && @raw["result"]["deposit"]
27
+ @raw["result"]["deposit"].to_f
28
+ else
29
+ 0.0
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ module Luosimao
2
+ module SMS
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,14 @@
1
+ require "net/http"
2
+ require "json"
3
+ require "uri"
4
+ require "openssl"
5
+
6
+ require_relative "sms/version"
7
+ require_relative "sms/error"
8
+ require_relative "sms/response"
9
+ require_relative "sms/client"
10
+
11
+ module Luosimao
12
+ module SMS
13
+ end
14
+ end
@@ -0,0 +1 @@
1
+ require_relative "luosimao/sms"
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: luosimao-sms
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - luosimao-oss
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: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.12'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.12'
26
+ - !ruby/object:Gem::Dependency
27
+ name: webmock
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.18'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.18'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '13.0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '13.0'
54
+ description: A Ruby gem for sending SMS via Luosimao (螺丝帽) API
55
+ email:
56
+ - luosimao-oss@example.com
57
+ executables: []
58
+ extensions: []
59
+ extra_rdoc_files: []
60
+ files:
61
+ - CHANGELOG.md
62
+ - LICENSE
63
+ - README.md
64
+ - lib/luosimao-sms.rb
65
+ - lib/luosimao/sms.rb
66
+ - lib/luosimao/sms/client.rb
67
+ - lib/luosimao/sms/error.rb
68
+ - lib/luosimao/sms/response.rb
69
+ - lib/luosimao/sms/version.rb
70
+ homepage: https://github.com/luosimao-oss/luosimao-sms-ruby
71
+ licenses:
72
+ - MIT
73
+ metadata: {}
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 2.7.0
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubygems_version: 4.0.6
89
+ specification_version: 4
90
+ summary: Ruby SDK for Luosimao SMS API
91
+ test_files: []