fbcrawl-colly 0.2.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module FbcrawlColly
2
+ class Client
3
+
4
+ def initialize(host_and_port)
5
+ @host_and_port = host_and_port
6
+ @client = new_grpc_client
7
+ @colly = @client.init(FbcrawlColly::Empty.new)
8
+
9
+ ObjectSpace.define_finalizer(self) do
10
+ new_grpc_client.free_colly(@colly)
11
+ end
12
+ end
13
+
14
+ def login(email, password, totp_secret = "")
15
+ s = @client.login(FbcrawlColly::LoginRequest.new(pointer: @colly, email: email, password: password, totp_secret: totp_secret)).cookies
16
+ end
17
+
18
+ def login_with_cookies(cookies)
19
+ s = @client.login_with_cookies(FbcrawlColly::LoginWithCookiesRequest.new(pointer: @colly, cookies: cookies))
20
+ end
21
+
22
+ def fetch_group_info(group_id_or_username)
23
+ s = @client.fetch_group_info(FbcrawlColly::FetchGroupInfoRequest.new(pointer: @colly, group_username: group_id_or_username))
24
+ end
25
+
26
+ def fetch_group_feed(group_id, next_cursor = nil)
27
+ s = @client.fetch_group_feed(FbcrawlColly::FetchGroupFeedRequest.new(pointer: @colly, group_id: group_id, next_cursor: next_cursor))
28
+ end
29
+
30
+ def fetch_post(group_id, post_id, comment_next_cursor = nil)
31
+ s = @client.fetch_post(FbcrawlColly::FetchPostRequest.new(pointer: @colly, group_id: group_id, post_id: post_id, comment_next_cursor: comment_next_cursor))
32
+ end
33
+
34
+ def fetch_content_images(post_id, next_cursor = nil)
35
+ s = @client.fetch_content_images(FbcrawlColly::FetchContentImagesRequest.new(pointer: @colly, post_id: post_id, next_cursor: next_cursor))
36
+ end
37
+
38
+ def fetch_image_url(image_id)
39
+ s = @client.fetch_image_url(FbcrawlColly::FetchImageUrlRequest.new(pointer: @colly, image_id: image_id))
40
+ end
41
+ private
42
+ def new_grpc_client
43
+ FbcrawlColly::Grpc::Stub.new(@host_and_port, :this_channel_is_insecure)
44
+ end
45
+ end
46
+ end
@@ -1,3 +1,3 @@
1
1
  module FbcrawlColly
2
- VERSION = "0.2.6"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -0,0 +1,117 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: fbcrawl.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ Google::Protobuf::DescriptorPool.generated_pool.build do
7
+ add_file("fbcrawl.proto", :syntax => :proto3) do
8
+ add_message "fbcrawl_colly.Empty" do
9
+ end
10
+ add_message "fbcrawl_colly.Pointer" do
11
+ optional :address, :int64, 1
12
+ end
13
+ add_message "fbcrawl_colly.LoginRequest" do
14
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
15
+ optional :email, :string, 2
16
+ optional :password, :string, 3
17
+ optional :totp_secret, :string, 4
18
+ end
19
+ add_message "fbcrawl_colly.LoginResponse" do
20
+ optional :cookies, :string, 1
21
+ end
22
+ add_message "fbcrawl_colly.LoginWithCookiesRequest" do
23
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
24
+ optional :cookies, :string, 2
25
+ end
26
+ add_message "fbcrawl_colly.FetchGroupInfoRequest" do
27
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
28
+ optional :group_username, :string, 2
29
+ end
30
+ add_message "fbcrawl_colly.FetchGroupFeedRequest" do
31
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
32
+ optional :group_id, :int64, 2
33
+ optional :next_cursor, :string, 3
34
+ end
35
+ add_message "fbcrawl_colly.FetchPostRequest" do
36
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
37
+ optional :group_id, :int64, 2
38
+ optional :post_id, :int64, 3
39
+ optional :comment_next_cursor, :string, 4
40
+ end
41
+ add_message "fbcrawl_colly.FetchContentImagesRequest" do
42
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
43
+ optional :post_id, :int64, 2
44
+ optional :next_cursor, :string, 3
45
+ end
46
+ add_message "fbcrawl_colly.FetchImageUrlRequest" do
47
+ optional :pointer, :message, 1, "fbcrawl_colly.Pointer"
48
+ optional :image_id, :int64, 2
49
+ end
50
+ add_message "fbcrawl_colly.FacebookGroup" do
51
+ optional :id, :int64, 1
52
+ optional :name, :string, 2
53
+ optional :member_count, :int64, 3
54
+ end
55
+ add_message "fbcrawl_colly.FacebookUser" do
56
+ optional :id, :int64, 1
57
+ optional :name, :string, 2
58
+ end
59
+ add_message "fbcrawl_colly.FacebookPost" do
60
+ optional :id, :int64, 1
61
+ optional :group, :message, 2, "fbcrawl_colly.FacebookGroup"
62
+ optional :user, :message, 3, "fbcrawl_colly.FacebookUser"
63
+ optional :content, :string, 4
64
+ optional :comments, :message, 5, "fbcrawl_colly.CommentList"
65
+ optional :content_link, :string, 6
66
+ repeated :content_images, :message, 7, "fbcrawl_colly.FacebookImage"
67
+ optional :content_image, :message, 8, "fbcrawl_colly.FacebookImage"
68
+ optional :created_at, :int64, 9
69
+ optional :reaction_count, :int64, 10
70
+ optional :comment_count, :int64, 11
71
+ end
72
+ add_message "fbcrawl_colly.CommentList" do
73
+ repeated :comments, :message, 5, "fbcrawl_colly.FacebookComment"
74
+ optional :next_cursor, :string, 12
75
+ end
76
+ add_message "fbcrawl_colly.FacebookImage" do
77
+ optional :id, :int64, 1
78
+ optional :url, :string, 2
79
+ end
80
+ add_message "fbcrawl_colly.FacebookComment" do
81
+ optional :id, :int64, 1
82
+ optional :post, :message, 2, "fbcrawl_colly.FacebookPost"
83
+ optional :user, :message, 3, "fbcrawl_colly.FacebookUser"
84
+ optional :content, :string, 4
85
+ optional :created_at, :int64, 5
86
+ end
87
+ add_message "fbcrawl_colly.FacebookPostList" do
88
+ repeated :posts, :message, 1, "fbcrawl_colly.FacebookPost"
89
+ optional :next_cursor, :string, 2
90
+ end
91
+ add_message "fbcrawl_colly.FacebookImageList" do
92
+ repeated :images, :message, 1, "fbcrawl_colly.FacebookImage"
93
+ optional :next_cursor, :string, 2
94
+ end
95
+ end
96
+ end
97
+
98
+ module FbcrawlColly
99
+ Empty = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.Empty").msgclass
100
+ Pointer = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.Pointer").msgclass
101
+ LoginRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.LoginRequest").msgclass
102
+ LoginResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.LoginResponse").msgclass
103
+ LoginWithCookiesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.LoginWithCookiesRequest").msgclass
104
+ FetchGroupInfoRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FetchGroupInfoRequest").msgclass
105
+ FetchGroupFeedRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FetchGroupFeedRequest").msgclass
106
+ FetchPostRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FetchPostRequest").msgclass
107
+ FetchContentImagesRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FetchContentImagesRequest").msgclass
108
+ FetchImageUrlRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FetchImageUrlRequest").msgclass
109
+ FacebookGroup = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookGroup").msgclass
110
+ FacebookUser = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookUser").msgclass
111
+ FacebookPost = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookPost").msgclass
112
+ CommentList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.CommentList").msgclass
113
+ FacebookImage = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookImage").msgclass
114
+ FacebookComment = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookComment").msgclass
115
+ FacebookPostList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookPostList").msgclass
116
+ FacebookImageList = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("fbcrawl_colly.FacebookImageList").msgclass
117
+ end
@@ -0,0 +1,31 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: fbcrawl.proto for package 'fbcrawl_colly'
3
+
4
+ require 'grpc'
5
+ require 'fbcrawl_pb'
6
+
7
+ module FbcrawlColly
8
+ module Grpc
9
+ class Service
10
+
11
+ include GRPC::GenericService
12
+
13
+ self.marshal_class_method = :encode
14
+ self.unmarshal_class_method = :decode
15
+ self.service_name = 'fbcrawl_colly.Grpc'
16
+
17
+ # Sends a greeting
18
+ rpc :Init, FbcrawlColly::Empty, FbcrawlColly::Pointer
19
+ rpc :FreeColly, FbcrawlColly::Pointer, FbcrawlColly::Empty
20
+ rpc :Login, FbcrawlColly::LoginRequest, FbcrawlColly::LoginResponse
21
+ rpc :LoginWithCookies, FbcrawlColly::LoginWithCookiesRequest, FbcrawlColly::Empty
22
+ rpc :FetchGroupInfo, FbcrawlColly::FetchGroupInfoRequest, FbcrawlColly::FacebookGroup
23
+ rpc :FetchGroupFeed, FbcrawlColly::FetchGroupFeedRequest, FbcrawlColly::FacebookPostList
24
+ rpc :FetchPost, FbcrawlColly::FetchPostRequest, FbcrawlColly::FacebookPost
25
+ rpc :FetchContentImages, FbcrawlColly::FetchContentImagesRequest, FbcrawlColly::FacebookImageList
26
+ rpc :FetchImageUrl, FbcrawlColly::FetchImageUrlRequest, FbcrawlColly::FacebookImage
27
+ end
28
+
29
+ Stub = Service.rpc_stub_class
30
+ end
31
+ end
data/main.go CHANGED
@@ -1,21 +1,17 @@
1
1
  package main
2
2
 
3
- /*
4
- #include <stdio.h>
5
- #include <stdlib.h>
6
-
7
- static void myprint(char* s) {
8
- printf("%s\n", s);
9
- }
10
-
11
-
12
- */
13
3
  import "C"
14
-
15
4
  import (
5
+ context "context"
16
6
  "flag"
17
- "github.com/golang/protobuf/proto"
18
- "qnetwork.net/fbcrawl/fbcolly"
7
+ "github.com/google/logger"
8
+ lru "github.com/hashicorp/golang-lru"
9
+ "google.golang.org/grpc"
10
+ "log"
11
+ "net"
12
+ "os"
13
+ "qnetwork.net/fbcrawl/fbcrawl"
14
+ "qnetwork.net/fbcrawl/fbcrawl/pb"
19
15
  "unsafe"
20
16
  )
21
17
 
@@ -27,92 +23,93 @@ var password = flag.String("password", "change_me", "facebook password")
27
23
  var otp = flag.String("otp", "123456", "facebook otp")
28
24
  var groupId = flag.String("groupId", "334294967318328", "facebook group id, default is 334294967318328")
29
25
 
30
- var allInstances = map[uintptr]*fbcolly.Fbcolly{}
26
+ var allInstances, _ = lru.New(20)
27
+
28
+ func getColly(pointer *pb.Pointer) *fbcolly.Fbcolly {
29
+ c, ok := allInstances.Get(pointer.Address)
30
+ if ok {
31
+ return c.(*fbcolly.Fbcolly)
32
+ }
33
+ return nil
34
+ }
35
+
36
+ // server is used to implement helloworld.GreeterServer.
37
+ type server struct {
38
+ pb.GrpcServer
39
+ }
31
40
 
32
- //export Init
33
- func Init() uintptr {
41
+ func (s server) Init(ctx context.Context, empty *pb.Empty) (*pb.Pointer, error) {
34
42
  instance := fbcolly.New()
35
43
  ptr := (uintptr)(unsafe.Pointer(instance))
36
- allInstances[ptr] = instance
37
- return ptr
44
+ allInstances.Add(int64(ptr), instance)
45
+ return &pb.Pointer{Address: int64(ptr)}, nil
38
46
  }
39
47
 
40
- //export FreeColly
41
- func FreeColly(pointer unsafe.Pointer) {
42
- delete(allInstances, uintptr(pointer))
48
+ func (s server) FreeColly(ctx context.Context, pointer *pb.Pointer) (*pb.Empty, error) {
49
+ logger.Info("FreeColly")
50
+ allInstances.Remove(pointer.Address)
51
+ return &pb.Empty{}, nil
43
52
  }
44
53
 
45
- //export Login
46
- func Login(pointer unsafe.Pointer, email *C.char, password *C.char) *C.char {
47
- p := (*fbcolly.Fbcolly)(pointer)
48
- cookies, err := p.Login(C.GoString(email), C.GoString(password), "")
54
+ func (s server) Login(ctx context.Context, request *pb.LoginRequest) (*pb.LoginResponse, error) {
55
+ p := getColly(request.Pointer)
56
+
57
+ cookies, err := p.Login(request.Email, request.Password, request.TotpSecret)
49
58
  if err == nil {
50
- return C.CString(cookies)
59
+ return &pb.LoginResponse{Cookies: cookies}, err
51
60
  }
52
- return nil
61
+ return nil, err
53
62
  }
54
63
 
55
- //export LoginWithCookies
56
- func LoginWithCookies(pointer unsafe.Pointer, cookies *C.char) {
57
- p := (*fbcolly.Fbcolly)(pointer)
58
- p.LoginWithCookies(C.GoString(cookies))
64
+ func (s server) LoginWithCookies(ctx context.Context, request *pb.LoginWithCookiesRequest) (*pb.Empty, error) {
65
+ p := getColly(request.Pointer)
66
+ err := p.LoginWithCookies(request.Cookies)
67
+ return &pb.Empty{}, err
59
68
  }
60
69
 
61
- //export FetchGroupInfo
62
- func FetchGroupInfo(pointer unsafe.Pointer, groupIdOrUsername *C.char) unsafe.Pointer {
63
- p := (*fbcolly.Fbcolly)(pointer)
64
- _, groupInfo := p.FetchGroupInfo(C.GoString(groupIdOrUsername))
65
- marshaled, _ := proto.Marshal(groupInfo)
66
- return C.CBytes(append(marshaled, 0))
70
+ func (s server) FetchGroupInfo(ctx context.Context, request *pb.FetchGroupInfoRequest) (*pb.FacebookGroup, error) {
71
+ p := getColly(request.Pointer)
72
+ err, groupInfo := p.FetchGroupInfo(request.GroupUsername)
73
+ return groupInfo, err
67
74
  }
68
75
 
69
- //export FetchGroupFeed
70
- func FetchGroupFeed(pointer unsafe.Pointer, groupId int64) unsafe.Pointer {
71
- p := (*fbcolly.Fbcolly)(pointer)
72
- _, postsList := p.FetchGroupFeed(groupId)
73
- marshaledPostsList, _ := proto.Marshal(postsList)
74
- return C.CBytes(append(marshaledPostsList, 0))
76
+ func (s server) FetchGroupFeed(ctx context.Context, request *pb.FetchGroupFeedRequest) (*pb.FacebookPostList, error) {
77
+ p := getColly(request.Pointer)
78
+ err, postsList := p.FetchGroupFeed(request.GroupId, request.NextCursor)
79
+ return postsList, err
75
80
  }
76
81
 
77
- //export FetchPost
78
- func FetchPost(pointer unsafe.Pointer, groupId int64, postId int64) unsafe.Pointer {
79
- p := (*fbcolly.Fbcolly)(pointer)
80
- _, post := p.FetchPost(groupId, postId)
81
- marshaledPost, _ := proto.Marshal(post)
82
- return C.CBytes(append(marshaledPost, 0))
82
+ func (s server) FetchPost(ctx context.Context, request *pb.FetchPostRequest) (*pb.FacebookPost, error) {
83
+ p := getColly(request.Pointer)
84
+ err, post := p.FetchPost(request.GroupId, request.PostId, request.CommentNextCursor)
85
+ return post, err
83
86
  }
84
87
 
85
- //export FetchContentImages
86
- func FetchContentImages(pointer unsafe.Pointer, postId int64) unsafe.Pointer {
87
- p := (*fbcolly.Fbcolly)(pointer)
88
- _, imageList := p.FetchContentImages(postId)
89
- marshaled, _ := proto.Marshal(imageList)
90
- return C.CBytes(append(marshaled, 0))
88
+ func (s server) FetchContentImages(ctx context.Context, request *pb.FetchContentImagesRequest) (*pb.FacebookImageList, error) {
89
+ p := getColly(request.Pointer)
90
+ err, imageList := p.FetchContentImages(request.PostId, request.NextCursor)
91
+ return imageList, err
91
92
  }
92
93
 
93
- //export FetchImageUrl
94
- func FetchImageUrl(pointer unsafe.Pointer, imageId int64) unsafe.Pointer {
95
- p := (*fbcolly.Fbcolly)(pointer)
96
- _, image := p.FetchImageUrl(imageId)
97
- marshaled, _ := proto.Marshal(image)
98
- return C.CBytes(append(marshaled, 0))
94
+ func (s server) FetchImageUrl(ctx context.Context, request *pb.FetchImageUrlRequest) (*pb.FacebookImage, error) {
95
+ p := getColly(request.Pointer)
96
+ err, image := p.FetchImageUrl(request.ImageId)
97
+ return image, err
99
98
  }
100
99
 
101
100
  func main() {
102
- //r := regexp.MustCompile("/([\\d\\w.]+)").FindStringSubmatch()[1]
103
- //print(r.FindStringSubmatch("/liem.phamthanh.161?refid=18&__tn__=R")[1])
104
- //flag.Parse()
105
- //
106
- ////post := fbcrawl.FacebookPost{}
107
- ////bPost, err := proto.Marshal(&post)
108
- //
109
- //lf, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0660)
110
- //if err != nil {
111
- // logger.Fatalf("Failed to open log file: %v", err)
112
- //}
113
- //defer lf.Close()
114
- //defer logger.Init("fb-colly", *verbose, false, lf).Close()
115
- //f := fbcolly.New()
116
- //err = f.Login(*email, *password, *otp)
117
- //f.FetchGroupFeed(*groupId)
101
+ port := os.Getenv("PORT")
102
+ if len(port) == 0 {
103
+ port = "50051"
104
+ }
105
+ lis, err := net.Listen("tcp", ":"+port)
106
+ logger.Info("Port listened at", port)
107
+ if err != nil {
108
+ log.Fatalf("failed to listen: %v", err)
109
+ }
110
+ s := grpc.NewServer()
111
+ pb.RegisterGrpcServer(s, &server{})
112
+ if err := s.Serve(lis); err != nil {
113
+ log.Fatalf("failed to serve: %v", err)
114
+ }
118
115
  }
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fbcrawl-colly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.6
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duy Le
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-15 00:00:00.000000000 Z
11
+ date: 2020-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: ffi
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: google-protobuf
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -39,13 +25,13 @@ dependencies:
39
25
  - !ruby/object:Gem::Version
40
26
  version: '0'
41
27
  - !ruby/object:Gem::Dependency
42
- name: rake-compiler
28
+ name: grpc
43
29
  requirement: !ruby/object:Gem::Requirement
44
30
  requirements:
45
31
  - - ">="
46
32
  - !ruby/object:Gem::Version
47
33
  version: '0'
48
- type: :development
34
+ type: :runtime
49
35
  prerelease: false
50
36
  version_requirements: !ruby/object:Gem::Requirement
51
37
  requirements:
@@ -56,12 +42,12 @@ description: Crawl mbasic.facebook.com using GO Colly
56
42
  email:
57
43
  - duyleekun@gmail.com
58
44
  executables: []
59
- extensions:
60
- - ext/fbcrawl_colly/extconf.rb
45
+ extensions: []
61
46
  extra_rdoc_files: []
62
47
  files:
63
48
  - ".gitignore"
64
49
  - CODE_OF_CONDUCT.md
50
+ - Dockerfile
65
51
  - Gemfile
66
52
  - Gemfile.lock
67
53
  - LICENSE.txt
@@ -69,18 +55,19 @@ files:
69
55
  - Rakefile
70
56
  - bin/console
71
57
  - bin/setup
72
- - ext/fbcrawl_colly/.gitignore
73
- - ext/fbcrawl_colly/Makefile
74
- - ext/fbcrawl_colly/extconf.rb
75
- - fbcolly/fbcolly.go
76
58
  - fbcrawl-colly.gemspec
77
59
  - fbcrawl.proto
60
+ - fbcrawl/fbcolly.go
61
+ - fbcrawl/pb/fbcrawl.pb.go
62
+ - fbcrawl/pb/fbcrawl_grpc.pb.go
78
63
  - go.mod
79
64
  - go.sum
80
65
  - lib/fbcrawl-colly.rb
81
- - lib/fbcrawl_colly/colly.rb
82
- - lib/fbcrawl_colly/ffi.rb
66
+ - lib/fbcrawl_colly.rb
67
+ - lib/fbcrawl_colly/client.rb
83
68
  - lib/fbcrawl_colly/version.rb
69
+ - lib/pb/fbcrawl_pb.rb
70
+ - lib/pb/fbcrawl_services_pb.rb
84
71
  - main.go
85
72
  homepage: http://github.com/duyleekun/fbcrawl-colly
86
73
  licenses:
@@ -93,6 +80,7 @@ post_install_message:
93
80
  rdoc_options: []
94
81
  require_paths:
95
82
  - lib
83
+ - lib/pb
96
84
  required_ruby_version: !ruby/object:Gem::Requirement
97
85
  requirements:
98
86
  - - ">="