rucaptcha 3.1.4-x86_64-darwin → 3.2.2-x86_64-darwin
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 +4 -4
- data/README.md +16 -0
- data/Rakefile +10 -5
- data/ext/rucaptcha/src/captcha.rs +132 -48
- data/ext/rucaptcha/src/lib.rs +15 -3
- data/lib/rucaptcha/3.1/rucaptcha.bundle +0 -0
- data/lib/rucaptcha/3.2/rucaptcha.bundle +0 -0
- data/lib/rucaptcha/configuration.rb +8 -0
- data/lib/rucaptcha/engine.rb +1 -1
- data/lib/rucaptcha/version.rb +1 -1
- data/lib/rucaptcha.rb +5 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 898e6caf92b4973984791950c1cd43d631bafe5a583764d4548b1d2eb55d0c56
|
4
|
+
data.tar.gz: '0258e74e793b73548de21ea8c5656a447d207169246bd962fc5edfd25dde81fd'
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f5b05924369a6f5de341f023dc6d9d2efb61b12b21256d930b6038639c6848038c10a221c100cbcec189517ee054dab2cd07df2a6a46f2e2fbd76609d65e19a
|
7
|
+
data.tar.gz: cd0c2a3782639d272fe0e9db492d09afbdc20e5fa079060303f3ce7508e3a0adbffc7d3cdd86b4efbfa00a6696d687fc10ee73cc8b66cd22912175a912b0b94e
|
data/README.md
CHANGED
@@ -37,6 +37,7 @@ Create `config/initializers/rucaptcha.rb`
|
|
37
37
|
RuCaptcha.configure do
|
38
38
|
# Custom captcha code expire time if you need, default: 2 minutes
|
39
39
|
# self.expires_in = 120
|
40
|
+
|
40
41
|
# [Requirement / 重要]
|
41
42
|
# Store Captcha code where, this config more like Rails config.cache_store
|
42
43
|
# default: Read config info from `Rails.application.config.cache_store`
|
@@ -44,10 +45,25 @@ RuCaptcha.configure do
|
|
44
45
|
# 默认:会从 Rails 配置的 cache_store 里面读取相同的配置信息,并尝试用可以运行的方式,用于存储验证码字符
|
45
46
|
# 但如果是 [:null_store, :memory_store, :file_store] 之类的,你可以通过下面的配置项单独给 RuCaptcha 配置 cache_store
|
46
47
|
self.cache_store = :mem_cache_store
|
48
|
+
|
49
|
+
# If you wants disable `cache_store` check warning, you can do it, default: false
|
47
50
|
# 如果想要 disable cache_store 的 warning,就设置为 true,default false
|
48
51
|
# self.skip_cache_store_check = true
|
52
|
+
|
49
53
|
# Chars length, default: 5, allows: [3 - 7]
|
50
54
|
# self.length = 5
|
55
|
+
|
56
|
+
# Enable or disable Strikethrough, default: true
|
57
|
+
# self.line = true
|
58
|
+
|
59
|
+
# Enable or disable noise, default: false
|
60
|
+
# self.noise = false
|
61
|
+
|
62
|
+
# Set the image format, default: png, allows: [jpeg, png, webp]
|
63
|
+
# self.format = 'png'
|
64
|
+
|
65
|
+
# Custom mount path, default: '/rucaptcha'
|
66
|
+
# self.mount_path = '/rucaptcha'
|
51
67
|
end
|
52
68
|
```
|
53
69
|
|
data/Rakefile
CHANGED
@@ -27,9 +27,14 @@ end
|
|
27
27
|
RSpec::Core::RakeTask.new(:spec)
|
28
28
|
task default: :spec
|
29
29
|
|
30
|
+
def create_captcha(length = 5, difficulty = 5)
|
31
|
+
require "rucaptcha"
|
32
|
+
RuCaptchaCore.create(length, difficulty, false, false, "png")
|
33
|
+
end
|
34
|
+
|
30
35
|
task :preview do
|
31
36
|
require "rucaptcha"
|
32
|
-
res =
|
37
|
+
res = create_captcha()
|
33
38
|
warn "-------------------------\n#{res[0]}"
|
34
39
|
puts res[1].pack("c*")
|
35
40
|
end
|
@@ -38,11 +43,11 @@ task :memory do
|
|
38
43
|
require "rucaptcha"
|
39
44
|
require "memory_profiler"
|
40
45
|
|
41
|
-
|
46
|
+
create_captcha()
|
42
47
|
|
43
48
|
report = MemoryProfiler.report do
|
44
49
|
1000.times do
|
45
|
-
|
50
|
+
create_captcha()
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
@@ -57,10 +62,10 @@ task :benchmark do
|
|
57
62
|
require "rucaptcha"
|
58
63
|
require "benchmark/ips"
|
59
64
|
|
60
|
-
RuCaptchaCore.create(5, 5)
|
65
|
+
RuCaptchaCore.create(5, 5, true, true, "png")
|
61
66
|
|
62
67
|
Benchmark.ips do |x|
|
63
|
-
x.report("Generate image") { RuCaptchaCore.create(5, 5) }
|
68
|
+
x.report("Generate image") { RuCaptchaCore.create(5, 5, true, true, "png") }
|
64
69
|
x.compare!
|
65
70
|
end
|
66
71
|
end
|
@@ -1,6 +1,4 @@
|
|
1
|
-
use image::{ImageBuffer,
|
2
|
-
use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_text_mut};
|
3
|
-
use imageproc::noise::gaussian_noise_mut;
|
1
|
+
use image::{ImageBuffer, Rgba};
|
4
2
|
use rand::{thread_rng, Rng};
|
5
3
|
use rusttype::{Font, Scale};
|
6
4
|
use std::io::Cursor;
|
@@ -15,21 +13,21 @@ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regula
|
|
15
13
|
static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
|
16
14
|
|
17
15
|
// https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
|
18
|
-
static COLORS: [(u8, u8, u8); 14] = [
|
19
|
-
(197, 166, 3),
|
20
|
-
(187, 87, 5),
|
21
|
-
(176, 7, 7),
|
22
|
-
(186, 9, 56),
|
23
|
-
(204, 11, 143),
|
24
|
-
(124, 10, 190),
|
25
|
-
(87, 0, 200),
|
26
|
-
(61, 86, 168),
|
27
|
-
(63, 166, 126),
|
28
|
-
(69, 187, 48),
|
29
|
-
(105, 208, 3),
|
30
|
-
(160, 208, 3),
|
31
|
-
(216, 219, 2),
|
32
|
-
(50, 50, 50),
|
16
|
+
static COLORS: [(u8, u8, u8, u8); 14] = [
|
17
|
+
(197, 166, 3, 255),
|
18
|
+
(187, 87, 5, 255),
|
19
|
+
(176, 7, 7, 255),
|
20
|
+
(186, 9, 56, 255),
|
21
|
+
(204, 11, 143, 255),
|
22
|
+
(124, 10, 190, 255),
|
23
|
+
(87, 0, 200, 255),
|
24
|
+
(61, 86, 168, 255),
|
25
|
+
(63, 166, 126, 255),
|
26
|
+
(69, 187, 48, 255),
|
27
|
+
(105, 208, 3, 255),
|
28
|
+
(160, 208, 3, 255),
|
29
|
+
(216, 219, 2, 255),
|
30
|
+
(50, 50, 50, 255),
|
33
31
|
];
|
34
32
|
|
35
33
|
static SCALE_SM: u32 = 32;
|
@@ -51,18 +49,18 @@ fn get_captcha(len: usize) -> Vec<String> {
|
|
51
49
|
}
|
52
50
|
|
53
51
|
#[allow(unused)]
|
54
|
-
fn get_color() ->
|
52
|
+
fn get_color() -> Rgba<u8> {
|
55
53
|
let rnd = rand_num(COLORS.len() - 1);
|
56
54
|
let c = COLORS[rnd];
|
57
|
-
|
55
|
+
Rgba([c.0, c.1, c.2, c.3])
|
58
56
|
}
|
59
57
|
|
60
|
-
fn get_colors(num: usize) -> Vec<
|
58
|
+
fn get_colors(num: usize) -> Vec<Rgba<u8>> {
|
61
59
|
let rnd = rand_num(COLORS.len());
|
62
60
|
let mut out = vec![];
|
63
61
|
for i in 0..num {
|
64
62
|
let c = COLORS[(rnd + i) % COLORS.len()];
|
65
|
-
out.push(
|
63
|
+
out.push(Rgba([c.0, c.1, c.2, c.3]))
|
66
64
|
}
|
67
65
|
|
68
66
|
out
|
@@ -80,13 +78,13 @@ fn get_font() -> Font<'static> {
|
|
80
78
|
}
|
81
79
|
}
|
82
80
|
|
83
|
-
fn get_image(width: usize, height: usize) -> ImageBuffer<
|
81
|
+
fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
|
84
82
|
ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
|
85
|
-
image::
|
83
|
+
image::Rgba([255, 255, 255, 255])
|
86
84
|
})
|
87
85
|
}
|
88
86
|
|
89
|
-
fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<
|
87
|
+
fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
|
90
88
|
let c = (image.width() - 20) / res.len() as u32;
|
91
89
|
let y = image.height() / 3 - 15;
|
92
90
|
|
@@ -107,7 +105,10 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
107
105
|
// Draw line, ellipse first as background
|
108
106
|
for (i, _) in res.iter().enumerate() {
|
109
107
|
let line_color = line_colors[i];
|
110
|
-
|
108
|
+
|
109
|
+
if lines {
|
110
|
+
draw_interference_line(1, image, line_color);
|
111
|
+
}
|
111
112
|
draw_interference_ellipse(1, image, line_color);
|
112
113
|
}
|
113
114
|
|
@@ -121,7 +122,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
121
122
|
for j in 0..(rand_num(3) + 1) as i32 {
|
122
123
|
// Draw text again with offset
|
123
124
|
let offset = j * (rand_num(2) as i32);
|
124
|
-
draw_text_mut(
|
125
|
+
imageproc::drawing::draw_text_mut(
|
125
126
|
image,
|
126
127
|
color,
|
127
128
|
10 + offset + (i as u32 * c) as i32,
|
@@ -137,7 +138,7 @@ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u
|
|
137
138
|
}
|
138
139
|
}
|
139
140
|
|
140
|
-
fn draw_interference_line(num: usize, image: &mut ImageBuffer<
|
141
|
+
fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
|
141
142
|
for _ in 0..num {
|
142
143
|
let width = image.width();
|
143
144
|
let height = image.height();
|
@@ -153,7 +154,7 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
|
|
153
154
|
let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
|
154
155
|
let ctrl_y2 = get_next(x1, height - 5);
|
155
156
|
// Randomly draw bezier curves
|
156
|
-
draw_cubic_bezier_curve_mut(
|
157
|
+
imageproc::drawing::draw_cubic_bezier_curve_mut(
|
157
158
|
image,
|
158
159
|
(x1, y1),
|
159
160
|
(x2, y2),
|
@@ -166,8 +167,8 @@ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
|
|
166
167
|
|
167
168
|
fn draw_interference_ellipse(
|
168
169
|
num: usize,
|
169
|
-
image: &mut ImageBuffer<
|
170
|
-
color:
|
170
|
+
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
|
171
|
+
color: Rgba<u8>,
|
171
172
|
) {
|
172
173
|
for _ in 0..num {
|
173
174
|
// max cycle width 20px
|
@@ -189,6 +190,9 @@ pub struct CaptchaBuilder {
|
|
189
190
|
width: usize,
|
190
191
|
height: usize,
|
191
192
|
complexity: usize,
|
193
|
+
line: bool,
|
194
|
+
noise: bool,
|
195
|
+
format: image::ImageFormat,
|
192
196
|
}
|
193
197
|
|
194
198
|
impl CaptchaBuilder {
|
@@ -198,6 +202,9 @@ impl CaptchaBuilder {
|
|
198
202
|
width: 220,
|
199
203
|
height: 70,
|
200
204
|
complexity: 5,
|
205
|
+
line: true,
|
206
|
+
noise: false,
|
207
|
+
format: image::ImageFormat::Png,
|
201
208
|
}
|
202
209
|
}
|
203
210
|
|
@@ -206,15 +213,26 @@ impl CaptchaBuilder {
|
|
206
213
|
self
|
207
214
|
}
|
208
215
|
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
216
|
+
pub fn line(mut self, line: bool) -> Self {
|
217
|
+
self.line = line;
|
218
|
+
self
|
219
|
+
}
|
220
|
+
|
221
|
+
pub fn noise(mut self, noise: bool) -> Self {
|
222
|
+
self.noise = noise;
|
223
|
+
self
|
224
|
+
}
|
225
|
+
|
226
|
+
pub fn format(mut self, format: &str) -> Self {
|
227
|
+
self.format = match format {
|
228
|
+
"png" => image::ImageFormat::Png,
|
229
|
+
"jpg" | "jpeg" => image::ImageFormat::Jpeg,
|
230
|
+
"webp" => image::ImageFormat::WebP,
|
231
|
+
_ => image::ImageFormat::Png,
|
232
|
+
};
|
213
233
|
|
214
|
-
|
215
|
-
|
216
|
-
// self
|
217
|
-
// }
|
234
|
+
self
|
235
|
+
}
|
218
236
|
|
219
237
|
pub fn complexity(mut self, complexity: usize) -> Self {
|
220
238
|
let mut complexity = complexity;
|
@@ -238,20 +256,86 @@ impl CaptchaBuilder {
|
|
238
256
|
let mut image = get_image(self.width, self.height);
|
239
257
|
|
240
258
|
// Loop to write the verification code string into the background image
|
241
|
-
cyclic_write_character(&res, &mut image);
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
259
|
+
cyclic_write_character(&res, &mut image, self.line);
|
260
|
+
|
261
|
+
if self.noise {
|
262
|
+
imageproc::noise::gaussian_noise_mut(
|
263
|
+
&mut image,
|
264
|
+
(self.complexity - 1) as f64,
|
265
|
+
((10 * self.complexity) - 10) as f64,
|
266
|
+
((5 * self.complexity) - 5) as u64,
|
267
|
+
);
|
268
|
+
}
|
249
269
|
|
250
270
|
let mut bytes: Vec<u8> = Vec::new();
|
251
271
|
image
|
252
|
-
.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::
|
272
|
+
.write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
|
253
273
|
.unwrap();
|
254
274
|
|
255
275
|
Captcha { text, image: bytes }
|
256
276
|
}
|
257
277
|
}
|
278
|
+
|
279
|
+
#[cfg(test)]
|
280
|
+
mod tests {
|
281
|
+
use super::*;
|
282
|
+
|
283
|
+
#[test]
|
284
|
+
fn test_format() {
|
285
|
+
let mut builder = CaptchaBuilder::new();
|
286
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
287
|
+
|
288
|
+
builder = builder.format("jpg");
|
289
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
290
|
+
builder = builder.format("jpeg");
|
291
|
+
assert_eq!(builder.format, image::ImageFormat::Jpeg);
|
292
|
+
builder = builder.format("webp");
|
293
|
+
assert_eq!(builder.format, image::ImageFormat::WebP);
|
294
|
+
builder = builder.format("png");
|
295
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
296
|
+
builder = builder.format("gif");
|
297
|
+
assert_eq!(builder.format, image::ImageFormat::Png);
|
298
|
+
}
|
299
|
+
|
300
|
+
#[test]
|
301
|
+
fn test_line() {
|
302
|
+
let mut builder = CaptchaBuilder::new();
|
303
|
+
assert!(builder.line);
|
304
|
+
|
305
|
+
builder = builder.line(false);
|
306
|
+
assert!(!builder.line);
|
307
|
+
}
|
308
|
+
|
309
|
+
#[test]
|
310
|
+
fn test_noise() {
|
311
|
+
let mut builder = CaptchaBuilder::new();
|
312
|
+
assert!(!builder.noise);
|
313
|
+
|
314
|
+
builder = builder.noise(true);
|
315
|
+
assert!(builder.noise);
|
316
|
+
}
|
317
|
+
|
318
|
+
#[test]
|
319
|
+
fn test_difficulty() {
|
320
|
+
let mut builder = CaptchaBuilder::new();
|
321
|
+
assert_eq!(builder.complexity, 5);
|
322
|
+
|
323
|
+
builder = builder.complexity(10);
|
324
|
+
assert_eq!(builder.complexity, 10);
|
325
|
+
|
326
|
+
builder = builder.complexity(11);
|
327
|
+
assert_eq!(builder.complexity, 10);
|
328
|
+
|
329
|
+
builder = builder.complexity(0);
|
330
|
+
assert_eq!(builder.complexity, 1);
|
331
|
+
}
|
332
|
+
|
333
|
+
#[test]
|
334
|
+
fn test_length() {
|
335
|
+
let mut builder = CaptchaBuilder::new();
|
336
|
+
assert_eq!(builder.length, 4);
|
337
|
+
|
338
|
+
builder = builder.length(10);
|
339
|
+
assert_eq!(builder.length, 10);
|
340
|
+
}
|
341
|
+
}
|
data/ext/rucaptcha/src/lib.rs
CHANGED
@@ -2,9 +2,21 @@ use magnus::{define_class, function, Error, Object};
|
|
2
2
|
|
3
3
|
mod captcha;
|
4
4
|
|
5
|
-
pub fn create(
|
5
|
+
pub fn create(
|
6
|
+
len: usize,
|
7
|
+
difficulty: usize,
|
8
|
+
line: bool,
|
9
|
+
noise: bool,
|
10
|
+
format: String,
|
11
|
+
) -> (String, Vec<u8>) {
|
6
12
|
let c = captcha::CaptchaBuilder::new();
|
7
|
-
let out = c
|
13
|
+
let out = c
|
14
|
+
.complexity(difficulty)
|
15
|
+
.length(len)
|
16
|
+
.line(line)
|
17
|
+
.noise(noise)
|
18
|
+
.format(&format)
|
19
|
+
.build();
|
8
20
|
|
9
21
|
(out.text, out.image)
|
10
22
|
}
|
@@ -12,7 +24,7 @@ pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
|
|
12
24
|
#[magnus::init]
|
13
25
|
fn init() -> Result<(), Error> {
|
14
26
|
let class = define_class("RuCaptchaCore", Default::default())?;
|
15
|
-
class.define_singleton_method("create", function!(create,
|
27
|
+
class.define_singleton_method("create", function!(create, 5))?;
|
16
28
|
|
17
29
|
Ok(())
|
18
30
|
}
|
Binary file
|
Binary file
|
@@ -9,7 +9,15 @@ module RuCaptcha
|
|
9
9
|
attr_accessor :length
|
10
10
|
# Hard mode, default: 5, allows: [1..10]
|
11
11
|
attr_accessor :difficulty
|
12
|
+
# Enable or disable strikethrough lines on captcha image, default: true
|
13
|
+
attr_accessor :line
|
14
|
+
# Enable or disable noise on captcha image, default: false
|
15
|
+
attr_accessor :noise
|
16
|
+
# Image format allow: ['jpeg', 'png', 'webp'], default: 'png'
|
17
|
+
attr_accessor :format
|
12
18
|
# skip_cache_store_check, default: false
|
13
19
|
attr_accessor :skip_cache_store_check
|
20
|
+
# custom rucaptcha mount path, default: '/rucaptcha'
|
21
|
+
attr_accessor :mount_path
|
14
22
|
end
|
15
23
|
end
|
data/lib/rucaptcha/engine.rb
CHANGED
@@ -6,7 +6,7 @@ module RuCaptcha
|
|
6
6
|
# https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/routing/route_set.rb#L268
|
7
7
|
# `app.routes.prepend` start from Rails 3.2 - 5.0
|
8
8
|
app.routes.prepend do
|
9
|
-
mount RuCaptcha::Engine =>
|
9
|
+
mount RuCaptcha::Engine => RuCaptcha.config.mount_path
|
10
10
|
end
|
11
11
|
|
12
12
|
RuCaptcha.check_cache_store! unless RuCaptcha.config.skip_cache_store_check
|
data/lib/rucaptcha/version.rb
CHANGED
data/lib/rucaptcha.rb
CHANGED
@@ -28,6 +28,10 @@ module RuCaptcha
|
|
28
28
|
@config.difficulty = 3
|
29
29
|
@config.expires_in = 2.minutes
|
30
30
|
@config.skip_cache_store_check = false
|
31
|
+
@config.line = true
|
32
|
+
@config.noise = true
|
33
|
+
@config.format = "png"
|
34
|
+
@config.mount_path = "/rucaptcha"
|
31
35
|
|
32
36
|
@config.cache_store = if Rails.application
|
33
37
|
Rails.application.config.cache_store
|
@@ -47,7 +51,7 @@ module RuCaptcha
|
|
47
51
|
|
48
52
|
raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
|
49
53
|
|
50
|
-
result = RuCaptchaCore.create(length, config.difficulty || 5)
|
54
|
+
result = RuCaptchaCore.create(length, config.difficulty || 5, config.line, config.noise, config.format)
|
51
55
|
[result[0].downcase, result[1].pack("c*")]
|
52
56
|
end
|
53
57
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rucaptcha
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.2.2
|
5
5
|
platform: x86_64-darwin
|
6
6
|
authors:
|
7
7
|
- Jason Lee
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-10-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|
@@ -93,7 +93,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
93
93
|
- !ruby/object:Gem::Version
|
94
94
|
version: '0'
|
95
95
|
requirements: []
|
96
|
-
rubygems_version: 3.4.
|
96
|
+
rubygems_version: 3.4.4
|
97
97
|
signing_key:
|
98
98
|
specification_version: 4
|
99
99
|
summary: Captcha Gem for Rails, which generates captcha image by Rust.
|