gruby 0.2.1

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.
@@ -0,0 +1,6 @@
1
+ # svn propset svn:ignore -F .svnignore .
2
+ # svn propedit svn:ignore .
3
+ doc
4
+ pkg
5
+ .rake_tasks
6
+ *.gem
@@ -0,0 +1,11 @@
1
+ == 0.2.1 / 2007-08-11
2
+ * added gruby.rb file
3
+
4
+ == 0.2.0 / 2007-08-05
5
+ * uri-open instead of socket in connection_params method
6
+ * few callback added
7
+ * readme files
8
+ * new rake file
9
+
10
+ == 0.1.0 / 2007-04-15
11
+ * initial version
@@ -0,0 +1,52 @@
1
+ == GRuby
2
+ http://gruby.rubyforge.org
3
+ http://fazibear.prv.pl
4
+
5
+ == DESCRIPTION:
6
+
7
+ Gadu-Gadu protocol implementation in Ruby language
8
+
9
+ == FEATURES/PROBLEMS:
10
+
11
+ * loggin
12
+ * set status
13
+ * set status messages
14
+ * send messages
15
+ * recive messages
16
+
17
+ == SYNOPSIS:
18
+
19
+ require File.dirname(__FILE__) + '/../lib/GRuby'
20
+
21
+ gg_uin = ''
22
+ gg_pass = ''
23
+
24
+ gg = GG::new_session :uin => gg_uin, :password => gg_pass
25
+ gg.on_recive_message do |m|
26
+ gg.set_status :status => GG::STATUS_AVAIL_DESCR, :description => m.message
27
+ gg.send_message :uin => m.sender, :message => "Status '#{m.message}' was set"
28
+ end
29
+ gg.set_status :status => GG::STATUS_AVAIL_DESCR, :description => "Send me new status :)"
30
+
31
+ gets
32
+
33
+ == REQUIREMENTS:
34
+
35
+ * ruby
36
+ * gadu-gadu account
37
+
38
+ == INSTALL:
39
+
40
+ * sudo gem install gruby
41
+
42
+ == LICENSE:
43
+
44
+ GRuby - Gadu-Gadu protocol implementation in Ruby language
45
+
46
+ Copyright (C) 2007 Michal Kalbarczyk
47
+
48
+ This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
49
+
50
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
51
+
52
+ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gemspec|
7
+ gemspec.name = "gruby"
8
+ gemspec.summary = "Gadu-Gadu protocol implementation in Ruby language"
9
+ gemspec.email = "fazibear@gmail.com"
10
+ gemspec.homepage = "http://github.com/fazibear/gruby"
11
+ gemspec.description = "Gadu-Gadu protocol implementation in Ruby language"
12
+ gemspec.authors = ["fazibear"]
13
+ end
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/GRuby'
4
+
5
+ gg_uin = ''
6
+ gg_pass = ''
7
+
8
+ gg = GG::new_session :uin => gg_uin, :password => gg_pass
9
+ gg.on_recive_message do |m|
10
+ gg.set_status :status => GG::STATUS_AVAIL_DESCR, :description => m.message
11
+ gg.send_message :uin => m.sender, :message => "Status '#{m.message}' was set"
12
+ end
13
+ gg.set_status :status => GG::STATUS_AVAIL_DESCR, :description => "Send me new status :)"
14
+
15
+ gets
@@ -0,0 +1,55 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{gruby}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["fazibear"]
12
+ s.date = %q{2009-11-23}
13
+ s.description = %q{Gadu-Gadu protocol implementation in Ruby language}
14
+ s.email = %q{fazibear@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "CHANGELOG.rdoc",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "examples/status.rb",
25
+ "gruby.gemspec",
26
+ "lib/GRuby/GG.rb",
27
+ "lib/GRuby/GG_constants.rb",
28
+ "lib/GRuby/GG_packets.rb",
29
+ "lib/GRuby/GG_utils.rb",
30
+ "lib/gruby.rb",
31
+ "test/test_GRuby.rb",
32
+ "test/test_helper.rb"
33
+ ]
34
+ s.homepage = %q{http://github.com/fazibear/gruby}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{Gadu-Gadu protocol implementation in Ruby language}
39
+ s.test_files = [
40
+ "test/test_helper.rb",
41
+ "test/test_GRuby.rb",
42
+ "examples/status.rb"
43
+ ]
44
+
45
+ if s.respond_to? :specification_version then
46
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
47
+ s.specification_version = 3
48
+
49
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
50
+ else
51
+ end
52
+ else
53
+ end
54
+ end
55
+
@@ -0,0 +1,145 @@
1
+ module GG
2
+
3
+ #
4
+ # Return new GG session
5
+ #
6
+ def self.new_session( params )
7
+ Session.new( params )
8
+ end
9
+
10
+ class Session
11
+
12
+ attr_reader :connected
13
+
14
+ def initialize( params )
15
+ @uin = params[:uin]
16
+ @password = params[:password]
17
+ @message_seq = 0
18
+ @connected = true
19
+ @logged = false
20
+ @socket = nil
21
+ @wait4login = true
22
+ @wait4login = params[:async] unless params[:async].nil?
23
+
24
+ # callbacks
25
+ @revice_msg_callback = nil
26
+ @login_failed_callback = lambda do exit end
27
+ @disconnecting_callback = lambda do exit end
28
+
29
+ socket_thread = Thread.new do
30
+ begin
31
+ params = GG::connection_params
32
+ @socket = TCPSocket.new( params['host'] , params['port'] )
33
+ while @connected do
34
+ begin
35
+ r_packet = PACKET.new
36
+ puts ":: Wait for data ..." if $DEBUG
37
+ r_packet.header = @socket.read r_packet.header_length
38
+ r_packet.data = @socket.read r_packet.length
39
+ rescue => e
40
+ @logged = false
41
+ @connected = false
42
+ end
43
+ case r_packet.type
44
+ when WELCOME
45
+ r_packet.type = PACKET_WELCOME
46
+ s_packet = PACKET.new
47
+ s_packet.type = PACKET_LOGIN60
48
+ s_packet.uin = @uin.to_i
49
+ s_packet.hash = GG::login_hash( @password, r_packet.seed )
50
+
51
+ @socket.write s_packet.packed
52
+ puts "++ #{s_packet} send" if $DEBUG
53
+ s_packet = s_packet.destroy
54
+ when LOGIN_OK
55
+ @logged = true
56
+ @wait4login = false
57
+ when RECV_MSG
58
+ r_packet.type = PACKET_RECV_MSG
59
+ @recive_msg_callback.call r_packet if @recive_msg_callback
60
+ when SEND_MSG_ACK
61
+ r_packet.type = PACKET_SEND_MSG_ACK
62
+ when LOGIN_FAILED
63
+ puts "!! Login failed" if $DEBUG
64
+ @login_failed_callback.call if @login_failed_callback
65
+ when DISCONNECTING
66
+ puts "!! Disconecting" if $DEBUG
67
+ @disconnecting_callback.call if @disconnecting_callback
68
+ else
69
+ puts "?? Unknown packet" if $DEBUG
70
+ end
71
+ r_packet = r_packet.destroy
72
+ end
73
+ puts ":: Disconected" if $DEBUG
74
+ rescue => e
75
+ puts "!! Exception: #{e}" if $DEBUG
76
+ puts "Something went wrong :)" unless $DEBUG
77
+ exit 0
78
+ end
79
+ end
80
+ while @wait4login do
81
+ sleep 1
82
+ end
83
+ end
84
+
85
+
86
+ #
87
+ # Recive message callback
88
+ #
89
+ def on_recive_message( &block )
90
+ @recive_msg_callback = block;
91
+ end
92
+
93
+ #
94
+ # Login failed callback
95
+ #
96
+ def on_login_failed( &block )
97
+ @login_failed = block;
98
+ end
99
+
100
+ #
101
+ # Disconnecting callback
102
+ #
103
+ def on_diconnect( &block )
104
+ @disconnecting_callback = block;
105
+ end
106
+
107
+ #
108
+ # Send message
109
+ # :uin => GG reciver number
110
+ # :message => message text
111
+ #
112
+ def send_message( params )
113
+ if @logged
114
+ s_packet = PACKET.new
115
+ s_packet.type = PACKET_SEND_MSG
116
+ s_packet.recipient = params[:uin]
117
+ s_packet.message = params[:message]
118
+
119
+ @socket.write s_packet.packed
120
+ puts "++ #{s_packet} send" if $DEBUG
121
+ s_packet = s_packet.destroy
122
+ end
123
+ end
124
+
125
+ #
126
+ # Set new status.
127
+ # :status => new status, see constans.
128
+ # :description => new description
129
+ #
130
+ def set_status( params )
131
+ if @logged
132
+ s_packet = PACKET.new
133
+ s_packet.type = PACKET_NEW_STATUS
134
+ s_packet.status = params[:status]
135
+ s_packet.description = params[:description]
136
+
137
+ @socket.write s_packet.packed
138
+ puts "++ #{s_packet} send" if $DEBUG
139
+ s_packet = s_packet.destroy
140
+ end
141
+ end
142
+
143
+ end
144
+ end
145
+
@@ -0,0 +1,42 @@
1
+ module GG
2
+ HEADER = 0x0000
3
+ NEW_STATUS = 0x0002
4
+ PONG = 0x0007
5
+ PING = 0x0008
6
+ SEND_MSG = 0x000b
7
+ LOGIN = 0x000c
8
+ ADD_NOTIFY = 0x000d
9
+ REMOVE_NOTIFY = 0x000e
10
+ NOTIFY_FIRST = 0x000f
11
+ NOTIFY_LAST = 0x0010
12
+ LIST_EMPTY = 0x0012
13
+ LOGIN_EXT = 0x0013
14
+ PUBDIR50_REQUEST = 0x0014
15
+ LOGIN60 = 0x0015
16
+ USERLIST_REQUEST = 0x0016
17
+
18
+ WELCOME = 0x0001
19
+ STATUS = 0x0002
20
+ LOGIN_OK = 0x0003
21
+ SEND_MSG_ACK = 0x0005
22
+ LOGIN_FAILED = 0x0009
23
+ RECV_MSG = 0x000a
24
+ DISCONNECTING = 0x000b
25
+ NOTIFY_REPLY = 0x000c
26
+ PUBDIR50_REPLY = 0x000e
27
+ STATUS60 = 0x000f
28
+ USERLIST_REPLY = 0x0010
29
+ NOTIFY_REPLY60 = 0x0011
30
+ NEED_EMAIL = 0x0014
31
+
32
+ STATUS_NOT_AVAIL = 0x0001
33
+ STATUS_NOT_AVAIL_DESCR = 0x0015
34
+ STATUS_AVAIL = 0x0002
35
+ STATUS_AVAIL_DESCR = 0x0004
36
+ STATUS_BUSY = 0x0003
37
+ STATUS_BUSY_DESCR = 0x0005
38
+ STATUS_INVISIBLE = 0x0014
39
+ STATUS_INVISIBLE_DESCR = 0x0016
40
+ STATUS_BLOCKED = 0x0006
41
+ STATUS_FRIENDS_MASK = 0x8000
42
+ end
@@ -0,0 +1,241 @@
1
+ module GG
2
+
3
+ #
4
+ # GG Packet class
5
+ #
6
+ class PACKET
7
+ def initialize
8
+ puts "++ #{self} created" if $DEBUG
9
+ end
10
+
11
+ def destroy
12
+ puts "++ #{self} destroyed" if $DEBUG
13
+ nil
14
+ end
15
+
16
+ def header=(data)
17
+ unpacked = data.unpack 'II'
18
+ @type, @length = unpacked
19
+ puts "++ #{self} recived header ... type:#{@type} length:#{@length}" if $DEBUG
20
+ end
21
+
22
+ def header_length
23
+ 8
24
+ end
25
+
26
+ def type
27
+ @type
28
+ end
29
+
30
+ def length
31
+ @length
32
+ end
33
+
34
+ def type=(type)
35
+ begin
36
+ self.extend type
37
+ puts "++ #{self} extend to #{type}" if $DEBUG
38
+ fields @data if @data
39
+ rescue => e
40
+ raise "class extend ... #{e}"
41
+ end
42
+ end
43
+
44
+ def data=(data)
45
+ @data = data
46
+ puts "++ #{self} recived data" if $DEBUG
47
+ end
48
+
49
+ def pack(pattern)
50
+ begin
51
+ puts "++ #{self} data packed" if $DEBUG
52
+ packet.pack pattern
53
+ rescue => e
54
+ raise "packet pack ... #{e}"
55
+ end
56
+ end
57
+ end
58
+
59
+ # RECV PACKETS #
60
+
61
+ module PACKET_WELCOME
62
+ attr_reader :seed
63
+
64
+ def type
65
+ WELCOME
66
+ end
67
+
68
+ def fields(data)
69
+ unpacked = data.unpack 'I'
70
+ @seed = unpacked[0]
71
+ end
72
+ end
73
+
74
+ module PACKET_RECV_MSG
75
+ attr_reader :sender # numer nadawcy
76
+ attr_reader :seq # numer sekwencyjny
77
+ attr_reader :time # czas nadania
78
+ attr_reader :class # klasa wiadomości
79
+ attr_reader :message # treść wiadomości
80
+
81
+ def type
82
+ RECV_MSG
83
+ end
84
+
85
+ def fields(data)
86
+ unpacked = data.unpack 'IIIIZ*'
87
+ @sender,
88
+ @seq,
89
+ @time,
90
+ @class,
91
+ @message = unpacked
92
+ end
93
+
94
+ end
95
+
96
+ module PACKET_SEND_MSG_ACK
97
+ attr_reader :status # stan wiadomości */
98
+ attr_reader :recipient # numer odbiorcy */
99
+ attr_reader :seq # numer sekwencyjny */
100
+
101
+ def type
102
+ SEND_MSG_ACK
103
+ end
104
+
105
+ def fields(data)
106
+ unpacked = data.unpack 'III'
107
+ @status,
108
+ @recipient,
109
+ @seq = unpacked
110
+ end
111
+ end
112
+
113
+ # SEND PACKECTS #
114
+
115
+ module PACKET_LOGIN60
116
+ attr_writer :uin # mój numerek
117
+ attr_writer :hash # hash hasła */
118
+ attr_writer :status # status na dzień dobry */
119
+ attr_writer :version # moja wersja klienta */
120
+ attr_writer :unknown1 # 0x00 */
121
+ attr_writer :local_ip # mój adres ip */
122
+ attr_writer :local_port # port, na którym słucham */
123
+ attr_writer :external_ip # zewnętrzny adres ip */
124
+ attr_writer :external_port # zewnętrzny port */
125
+ attr_writer :image_size # maksymalny rozmiar grafiki w KB */
126
+ attr_writer :unknown2 # 0xbe */
127
+ attr_writer :description # opis, nie musi wystąpić */
128
+ attr_writer :time # czas, nie musi wystąpić */
129
+
130
+ def type
131
+ LOGIN60
132
+ end
133
+
134
+ def packet
135
+ [
136
+ type,
137
+ length,
138
+ @uin ? @uin : 0,
139
+ @hash ? @hash : 0,
140
+ @status ? @status : 0,
141
+ @version ? @version : 0x22,
142
+ @unknown1 ? @unknown1 : 0,
143
+ @local_ip ? @local_ip : 0,
144
+ @local_port ? @local_port : 0,
145
+ @external_ip ? @external_ip : 0,
146
+ @external_port ? @external_port : 0,
147
+ @image_size ? @image_size : 0,
148
+ @unknown2 ? @unknown2 : 0xBE,
149
+ @description ? "#{@description}" : "\0",
150
+ @time ? @time : 0
151
+ ]
152
+ end
153
+
154
+ def length
155
+ 4+4+4+4+1+4+2+4+2+1+1+(@description ? @description.length : 1)+4
156
+ end
157
+
158
+ def packed
159
+ pack 'II IIIIcIsIsccA*I'
160
+ end
161
+
162
+ end
163
+
164
+ module PACKET_NEW_STATUS
165
+ attr_writer :status # na jaki zmienić? */
166
+ attr_writer :description # opis, nie musi wystąpić */
167
+ attr_writer :time # czas, nie musi wystąpić */
168
+
169
+ def type
170
+ NEW_STATUS
171
+ end
172
+
173
+ def packet
174
+ [
175
+ type,
176
+ length,
177
+ @status ? @status : 0,
178
+ @description ? "#{@description}" : "\0",
179
+ @time ? @time : 0
180
+ ]
181
+ end
182
+
183
+ def length
184
+ 4+(@description ? @description.length : 1)+4
185
+ end
186
+
187
+ def packed
188
+ pack 'II IA*I'
189
+ end
190
+ end
191
+
192
+ module PACKET_LIST_EMPTY
193
+ def type
194
+ LIST_EMPTY
195
+ end
196
+ def packet
197
+ [
198
+ type,
199
+ length
200
+ ]
201
+ end
202
+
203
+ def length
204
+ 0
205
+ end
206
+
207
+ def packed
208
+ pack 'II'
209
+ end
210
+ end
211
+
212
+ module PACKET_SEND_MSG
213
+ attr_writer :recipient # numer odbiorcy */
214
+ attr_writer :seq # numer sekwencyjny */
215
+ attr_writer :class # klasa wiadomości */
216
+ attr_writer :message # treść */
217
+
218
+ def type
219
+ SEND_MSG
220
+ end
221
+
222
+ def packet
223
+ [
224
+ type,
225
+ length,
226
+ @recipient ? @recipient : 0,
227
+ @seq ? @seq : 0,
228
+ @class ? @class : 0x0008,
229
+ @message ? "#{@message}" : "\0"
230
+ ]
231
+ end
232
+
233
+ def length
234
+ 4+4+4+@message.length
235
+ end
236
+
237
+ def packed
238
+ pack 'II IIIA*'
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,50 @@
1
+ module GG
2
+
3
+ #
4
+ # Return calculated password hash.
5
+ #
6
+ def self.login_hash ( password, seed )
7
+ x = 0;
8
+ y = seed;
9
+ z = 0;
10
+
11
+ password.each_byte do |char|
12
+ x = ( x & 0xffffff00 ) | char.to_i
13
+ y ^= x;
14
+ y &= 0xffffffff
15
+ y += x;
16
+ y &= 0xffffffff
17
+ x <<= 8;
18
+ x &= 0xffffffff
19
+ y ^= x;
20
+ y &= 0xffffffff
21
+ x <<= 8;
22
+ x &= 0xffffffff
23
+ y -= x;
24
+ y &= 0xffffffff
25
+ x <<= 8;
26
+ x &= 0xffffffff
27
+ y ^= x;
28
+ y &= 0xffffffff
29
+ z = y & 0x1f;
30
+ z &= 0xffffffff
31
+ y = (y << z) | (y >> (32 - z))
32
+ y &= 0xffffffff
33
+ end
34
+ return y;
35
+ end
36
+
37
+ #
38
+ # Return connection params returned from gg server.
39
+ #
40
+ def self.connection_params
41
+ require 'open-uri'
42
+ gg_connection = open("http://appmsg.gadu-gadu.pl/appsvc/appmsg4.asp?fmnumber=").readlines[0].split(/\s/)[2].split(':')
43
+ gg_connection_param = {
44
+ 'host' => gg_connection[0],
45
+ 'port' => gg_connection[1]
46
+ }
47
+ puts ":: Connection params ... host:#{gg_connection_param['host']} port:#{gg_connection_param['port']}" if $DEBUG
48
+ return gg_connection_param
49
+ end
50
+ end
@@ -0,0 +1 @@
1
+ Dir[File.join(File.dirname(__FILE__), 'GRuby/*.rb')].each { |lib| require lib }
@@ -0,0 +1,14 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ class TestGRuby < Test::Unit::TestCase
4
+
5
+ def setup
6
+ end
7
+
8
+ def test_hash
9
+ hash_t = GG::login_hash('GG_HASH_TEST', 12345)
10
+ hash_e = 2990357796
11
+ assert_equal( hash_t, hash_e )
12
+ end
13
+
14
+ end
@@ -0,0 +1,2 @@
1
+ require 'test/unit'
2
+ require File.dirname(__FILE__) + '/../lib/GRuby'
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gruby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - fazibear
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-23 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Gadu-Gadu protocol implementation in Ruby language
17
+ email: fazibear@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - .gitignore
26
+ - CHANGELOG.rdoc
27
+ - README.rdoc
28
+ - Rakefile
29
+ - VERSION
30
+ - examples/status.rb
31
+ - gruby.gemspec
32
+ - lib/GRuby/GG.rb
33
+ - lib/GRuby/GG_constants.rb
34
+ - lib/GRuby/GG_packets.rb
35
+ - lib/GRuby/GG_utils.rb
36
+ - lib/gruby.rb
37
+ - test/test_GRuby.rb
38
+ - test/test_helper.rb
39
+ has_rdoc: true
40
+ homepage: http://github.com/fazibear/gruby
41
+ licenses: []
42
+
43
+ post_install_message:
44
+ rdoc_options:
45
+ - --charset=UTF-8
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ version:
54
+ required_rubygems_version: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ version:
60
+ requirements: []
61
+
62
+ rubyforge_project:
63
+ rubygems_version: 1.3.5
64
+ signing_key:
65
+ specification_version: 3
66
+ summary: Gadu-Gadu protocol implementation in Ruby language
67
+ test_files:
68
+ - test/test_helper.rb
69
+ - test/test_GRuby.rb
70
+ - examples/status.rb