raccdoc 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ tmp/
2
+ coverage/
3
+ doc/
4
+ pkg/
5
+ *.gem
@@ -0,0 +1 @@
1
+ For changes, see the github source at http://github.com/minter/raccdoc/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 H. Wade Minter
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,14 @@
1
+ CHANGELOG
2
+ lib/raccdoc/connection.rb
3
+ lib/raccdoc/forum.rb
4
+ lib/raccdoc/post.rb
5
+ lib/raccdoc.rb
6
+ LICENSE
7
+ Manifest
8
+ raccdoc.gemspec
9
+ Rakefile
10
+ README.rdoc
11
+ test/raccdoc_connection_test.rb
12
+ test/raccdoc_forum_test.rb
13
+ test/raccdoc_post_test.rb
14
+ test/test_helper.rb
@@ -0,0 +1,92 @@
1
+ = Raccdoc
2
+
3
+ == DESCRIPTION
4
+
5
+ Raccdoc is an API into the data of several classic telnet-based BBS systems, including:
6
+
7
+ * ISCABBS[http://iscabbs.com/]
8
+ * InaraBBS[http://www.inarabbs.com/]
9
+ * Gestalt
10
+
11
+ This is a Ruby version of that API.
12
+
13
+ The Raccdoc API document can be found at http://dev.iscabbs.com/doku.php?id=raccdoc_protocol_documentation
14
+
15
+ == EXAMPLE
16
+
17
+ require 'raccdoc'
18
+
19
+ # Connect to the BBS as user Bugcrusher
20
+ bbs = Raccdoc::Connection.new(:host => 'bbs.iscabbs.com', :port => 6145, :user => 'Bugcrusher', :password => 'MyPass')
21
+
22
+ # Find out information on each forum
23
+ bbs.forums.each do |id, data|
24
+ print "On forum number #{id} named #{data[:name]}\n"
25
+ end
26
+
27
+ # Join the Babble forum
28
+ forum = bbs.jump("Babble")
29
+
30
+ # See if you have permission to post
31
+ print "Can you post in there? #{forum.post?}\n"
32
+
33
+ # Read post number 2280
34
+ post = forum.read(2280)
35
+
36
+ # Print out the post body for 2280
37
+ print "Body is #{post.body}\n"
38
+
39
+ # Make your own post
40
+ mypost = forum.post("Hello world!")
41
+
42
+ # Get information on it
43
+ print "My new post is id number #{mypost.id}\n"
44
+
45
+ # Now delete it
46
+ mypost.delete
47
+
48
+ # Log out
49
+ bbs.logout
50
+
51
+ == REQUIREMENTS
52
+
53
+ * Ruby 1.8
54
+ * An account on the BBS that you wish to access (unless doing Guest/read-only operations)
55
+ * Mocha (for tests)
56
+
57
+ == TODO & KNOWN ISSUES
58
+
59
+ * X-Message support
60
+ * Mail> support
61
+
62
+ == INSTALL
63
+
64
+ * gem sources -a http://gems.rubyforge.org/
65
+ * sudo gem install raccdoc
66
+
67
+ Then require 'raccdoc' in your Ruby program.
68
+
69
+ == LICENSE
70
+
71
+ (The MIT License)
72
+
73
+ Copyright (c) 2009 H. Wade Minter
74
+
75
+ Permission is hereby granted, free of charge, to any person obtaining
76
+ a copy of this software and associated documentation files (the
77
+ 'Software'), to deal in the Software without restriction, including
78
+ without limitation the rights to use, copy, modify, merge, publish,
79
+ distribute, sublicense, and/or sell copies of the Software, and to
80
+ permit persons to whom the Software is furnished to do so, subject to
81
+ the following conditions:
82
+
83
+ The above copyright notice and this permission notice shall be
84
+ included in all copies or substantial portions of the Software.
85
+
86
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
87
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
88
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
89
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
90
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
91
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
92
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,32 @@
1
+ require './lib/raccdoc.rb'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "raccdoc"
7
+ gemspec.summary = "Ruby interface into the Raccdoc BBS API"
8
+ gemspec.description = "A Ruby way to speak the Raccdoc protocol"
9
+ gemspec.email = "minter@lunenburg.org"
10
+ gemspec.homepage = "http://github.com/minter/raccdoc"
11
+ gemspec.authors = ["H. Wade Minter"]
12
+ end
13
+ Jeweler::GemcutterTasks.new
14
+ rescue LoadError
15
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
16
+ end
17
+
18
+ namespace :test do
19
+ desc 'Check test coverage'
20
+ task :coverage do
21
+ rm_f "coverage"
22
+ system("rcov -x '/Library/Ruby/Gems/1.8/gems/' --sort coverage #{File.join(File.dirname(__FILE__), 'test/*_test.rb')}")
23
+ system("open #{File.join(File.dirname(__FILE__), 'coverage/index.html')}") if PLATFORM['darwin']
24
+ end
25
+
26
+ desc 'Remove coverage products'
27
+ task :clobber_coverage do
28
+ rm_r 'coverage' rescue nil
29
+ end
30
+
31
+ end
32
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.10
@@ -0,0 +1,21 @@
1
+ module Raccdoc
2
+
3
+ VERSION = '0.0.10'
4
+ require 'socket'
5
+ require 'time'
6
+
7
+ $:.unshift(File.dirname(__FILE__))
8
+ require 'raccdoc/connection'
9
+ require 'raccdoc/forum'
10
+ require 'raccdoc/post'
11
+
12
+ end
13
+
14
+ class InvalidLogin < StandardError
15
+ end
16
+
17
+ class ConnectionError < StandardError
18
+ end
19
+
20
+ class ResponseError < StandardError
21
+ end
@@ -0,0 +1,108 @@
1
+ module Raccdoc
2
+ class Connection
3
+
4
+ attr_reader :host
5
+ attr_reader :post
6
+
7
+ # Creates a new Raccdoc connection. By default, it connects to bbs.iscabbs.com, port 6145, which is the
8
+ # ISCABBS server.
9
+ #
10
+ # If a :user and :password is not supplied, a guest/anonymous login is done. Most, if not all, posting is
11
+ # restricted under this account.
12
+ #
13
+ # If the TCP connection fails, or the login is rejected, and exception is raised.
14
+ def initialize(param_args = {:host => 'bbs.iscabbs.com', :port => '6145', :user => nil, :password => nil})
15
+ args = {:host => 'bbs.iscabbs.com', :port => '6145', :user => nil, :password => nil }
16
+
17
+ args.merge!(param_args)
18
+ begin
19
+ @socket = TCPSocket.new(args[:host],args[:port])
20
+ rescue Errno::ECONNREFUSED
21
+ raise ConnectionError, "Could not connect to #{args[:host]}, port #{args[:port]}\n"
22
+ end
23
+
24
+ begin
25
+ response = @socket.readline.chomp
26
+ rescue EOFError
27
+ raise ConnectionError, "Got an unexpected EOF from the remote end"
28
+ end
29
+
30
+ unless response.match(/^2/)
31
+ raise ConnectionError, response
32
+ end
33
+
34
+ if (args[:user] && args[:password])
35
+ @socket.puts("LOGIN #{args[:user]}\t#{args[:password]}")
36
+ response = @socket.readline.chomp
37
+ unless response.match(/^2/)
38
+ raise InvalidLogin, response
39
+ end
40
+ end
41
+ @host = args[:host]
42
+ @port = args[:port]
43
+ end
44
+
45
+ # Closes the Raccdoc connection, then closes the socket.
46
+ def logout
47
+ unless @socket.closed?
48
+ @socket.puts "QUIT"
49
+
50
+ response = @socket.readline.chomp
51
+ unless response.match(/^2/)
52
+ raise ConnectionError, response
53
+ end
54
+ @socket.close
55
+ end
56
+ return true
57
+ end
58
+
59
+ # Tests the TCP socket to make sure that it's still connected to the remote server.
60
+ def connected?
61
+ @socket.closed? ? false : true
62
+ end
63
+
64
+ # Sets the active forum on the server to the specified forum name or ID, and returns a new Raccdoc::Forum object.
65
+ #
66
+ # The forum can be specified by number (0), or name ("Lobby" or "Program")
67
+ def jump(forum = 0)
68
+ Raccdoc::Forum.new(@socket, forum.to_s)
69
+ end
70
+
71
+ # Returns a hash of forum information. The key is the forum ID number, and the value is a hash containing the following data:
72
+ #
73
+ # * name - The forum name
74
+ # * lastnote - The earliest post ID number in the forum
75
+ # * admin - The handle of the forum admin
76
+ #
77
+ # The argument can be one of: ALL (default), PUBLIC, PRIVATE, TODO, JOINED, NAMED, THREADS
78
+ # depending on server support
79
+ #
80
+ # Note that this does not return actual Raccdoc::Forum objects in the interest of saving resources, so you'll need to jump to the forum that you want.
81
+ def forums(type="all")
82
+ @socket.puts "LIST #{type}"
83
+ forums = Hash.new
84
+
85
+ response = @socket.readline.chomp
86
+ unless response.match(/^3/)
87
+ raise ResponseError, response
88
+ end
89
+
90
+ while line = @socket.readline.chomp
91
+ break if line.match(/^\.$/)
92
+ tmp = Hash.new
93
+ line.split(/\t/).each do |pair|
94
+ (key, value) = pair.split(/:/)
95
+ if key == "admin"
96
+ value = value.split('/')[1]
97
+ end
98
+ tmp[key.to_sym] = value
99
+ end
100
+ id = tmp[:topic].to_i
101
+ forums[id] = tmp
102
+ end
103
+
104
+ return forums
105
+ end
106
+
107
+ end
108
+ end
@@ -0,0 +1,204 @@
1
+ module Raccdoc
2
+ class Forum
3
+
4
+ attr_reader :id
5
+ attr_reader :name
6
+ attr_reader :admin
7
+ attr_reader :anonymous
8
+ attr_reader :private
9
+
10
+ # Sets the active forum on the server to the specified forum number (0) or name (Lobby, Program), then returns a new Raccdoc::Forum object.
11
+ #
12
+ # There are five instance variables exposed through attr_reader:
13
+ #
14
+ # * id - The forum ID number
15
+ # * name - The forum name
16
+ # * admin - The name of the forum administrator
17
+ # * anonymous - either true, false, or force, depending on whether the anonymous posting option is on, off, or required
18
+ # * private - True if the forum is private (invite-only), false otherwise
19
+ def initialize(socket, forum = 0)
20
+ @socket = socket
21
+ @socket.puts "TOPIC #{forum.to_s}"
22
+ response = @socket.readline.chomp
23
+ unless response.match(/^2/)
24
+ raise ResponseError, response
25
+ end
26
+
27
+ @headers = {}
28
+ tuples = response.split(/\t/)
29
+ tuples.delete_at(0)
30
+ tuples.each do |pair|
31
+ (key, value) = pair.split(/:/)
32
+ @headers[key.downcase.to_sym] = value
33
+ end
34
+
35
+ flags = @headers[:flags].split(',')
36
+
37
+ if flags.include?("forceanonymous")
38
+ @anonymous = "force"
39
+ elsif flags.include?("cananonymous")
40
+ @anonymous = true
41
+ else
42
+ @anonymous = false
43
+ end
44
+
45
+ @private = flags.include?("private") ? true : false
46
+
47
+ @id = @headers[:topic]
48
+ @name = @headers[:name]
49
+ @admin = @headers[:admin].split('/')[1]
50
+ end
51
+
52
+ # Returns an array of all current post IDs in the forum.
53
+ #
54
+ # Takes an optional range string of the form oldest_noteid-newest_noteid. The oldest or newest can be omitted as needed.
55
+ def noteids(range = "")
56
+ noteids = Array.new
57
+ @socket.puts("XHDR noteno #{range.to_s}")
58
+ response = @socket.readline.chomp
59
+ unless response.match(/^3/)
60
+ raise ResponseError, response
61
+ end
62
+
63
+ while line = @socket.readline.chomp
64
+ break if line.match(/^\.$/)
65
+ (tag,noteid) = line.split(/:/)
66
+ noteids.push(noteid.to_i)
67
+ end
68
+
69
+ return noteids
70
+ end
71
+
72
+ # Returns a hash of the forum information. Keys are:
73
+ #
74
+ # * from - The username of the user who last updated the FI
75
+ # * date - The date the forum information was last updated (a Time object)
76
+ # * body - The actual text of the FI
77
+ def forum_information
78
+ fi = Hash.new
79
+ @socket.puts("SHOW info")
80
+ response = @socket.readline.chomp
81
+ unless response.match(/^3/)
82
+ raise ResponseError, response
83
+ end
84
+
85
+ # Get header information
86
+ while line = @socket.readline.chomp
87
+ break if line.match(/^$/)
88
+ (key, value) = line.split(/: /)
89
+ if key.downcase == "date"
90
+ value = Time.parse(value)
91
+ end
92
+ fi[key.downcase.to_sym] = value
93
+ end
94
+
95
+ body = ""
96
+ while line = @socket.readline
97
+ break if line.match(/^\.$/)
98
+ body << line
99
+ end
100
+ fi[:body] = body
101
+
102
+ return fi
103
+ end
104
+
105
+ # Sets the current user's first-unread pointer to the specified number. Any posts greater than that number will be listed as unread.
106
+ def first_unread=(postid = 0)
107
+ @socket.puts("SETRC #{postid.to_s}")
108
+ response = @socket.readline.chomp
109
+ unless response.match(/^2/)
110
+ raise ResponseError, response
111
+ end
112
+ end
113
+
114
+ # Returns the post ID number of the last read post in the forum. Any posts greater than that number can be considered unread.
115
+ def first_unread
116
+ @socket.puts("SHOW rcval")
117
+ response = @socket.readline.chomp
118
+ unless response.match(/^2/)
119
+ raise ResponseError, response
120
+ end
121
+ response.match(/\d+.*?:\s+(\d+)/)
122
+ return $1
123
+ end
124
+
125
+ # Returns a hash of information about the posts in the forum. The hash is keyed off of the post ID, and the value is a hash containing the following information:
126
+ #
127
+ # * author - The username of the user who created the post
128
+ # * date - The date the post was created or, if the post is anonymous, the current date in UTC
129
+ # * subject - The first line of the post
130
+ # * size - The size of the post in bytes
131
+ # * authority - If the post was made with Sysop or Forum Manager status, this is set
132
+ #
133
+ # You may provide an optional range (in the form "start_id-end_id") to limit the number of posts returned. The default is to return all posts in the forum.
134
+ def post_headers(range = "")
135
+ @socket.puts("XHDR ALL #{range.to_s}")
136
+ response = @socket.readline.chomp
137
+ unless response.match(/^3/)
138
+ raise ResponseError, response
139
+ end
140
+
141
+ posts = Hash.new
142
+
143
+ while (line = @socket.readline.chomp)
144
+ break if line.match(/^\.$/)
145
+ tmpdata = Hash.new
146
+ line.split(/\t/).each do |tuple|
147
+ (key, value) = tuple.split(/:/, 2)
148
+ tmpdata[key.downcase.to_sym] = value
149
+ end
150
+ tmpdata[:date] = tmpdata[:date] ? Time.parse(tmpdata[:date]) : Time.new.getgm
151
+ tmpdata[:author] = tmpdata[:"formal-author"] ? tmpdata[:"formal-author"].split('/')[1] : 'Anonymous'
152
+ posts[tmpdata[:noteno]] = tmpdata
153
+ end
154
+ return posts
155
+ end
156
+
157
+ # Returns a new Raccdoc::Post object for the specified post ID in the current forum.
158
+ def read(postid)
159
+ Post.new(@socket,postid)
160
+ end
161
+
162
+ # Attempts to do a "DAMMIT" read, overriding anonymous and deleted flags. If you do not have permission to bypass those flags, this is equivalent to a plain read.
163
+ def read!(postid)
164
+ Post.new(@socket,postid,:dammit => true)
165
+ end
166
+
167
+ # Creates a new post in the current forum using the text provided in the argument.
168
+ #
169
+ # Returns a new Raccdoc::Post object that results from reading the newly created post.
170
+ def post(body)
171
+ @socket.puts("POST")
172
+ response = @socket.readline.chomp
173
+ unless response.match(/^3/)
174
+ raise ResponseError, response
175
+ end
176
+
177
+ @socket.puts(body)
178
+ @socket.puts(".")
179
+
180
+ response = @socket.readline.chomp
181
+ if response.match(/^2/)
182
+ postid = response.split(/:\s+/)[1]
183
+ return Post.new(@socket,postid)
184
+ else
185
+ raise ResponseError, response
186
+ end
187
+ end
188
+
189
+ # Checks to see if the currently logged in user has permission to post in the current forum.
190
+ #
191
+ # Returns true or false.
192
+ def post?
193
+ @socket.puts("OKAY POST")
194
+ response = @socket.readline.chomp
195
+ if response.match(/^2/)
196
+ return true
197
+ elsif response.match(/^4/)
198
+ return false
199
+ else
200
+ raise ResponseError, response
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,83 @@
1
+ module Raccdoc
2
+ class Post
3
+
4
+ attr_reader :id
5
+ attr_reader :date
6
+ attr_reader :author
7
+ attr_reader :body
8
+ attr_reader :authority
9
+
10
+ # Creates a new Raccdoc::Post object by reading the specified post. If no post is given, it returns the first unread post, if any.
11
+ #
12
+ # Useful information returned:
13
+ #
14
+ # * id - The numerical post ID
15
+ # * date - The date the post was made (or the current date in UTC if the post is anonymous)
16
+ # * author - The username of the user that created the post
17
+ # * body - The textual body of the post.
18
+ # * authority - either "Sysop" or "Forum Moderator" if the post was made with that header.
19
+ def initialize(socket,postid,args={})
20
+ @socket = socket
21
+ dammit = args[:dammit] ? 'DAMMIT' : ''
22
+ @socket.puts "READ #{postid.to_s} #{dammit}"
23
+ response = @socket.readline.chomp
24
+ unless response.match(/^3/)
25
+ raise ResponseError, response
26
+ end
27
+
28
+ # Get header information
29
+ post = Hash.new
30
+
31
+ while line = @socket.readline.chomp
32
+ break if line.match(/^$/)
33
+ (key,value) = line.split(/: /)
34
+ post[key.downcase.to_sym] = value
35
+ end
36
+ post[:id] = postid
37
+ post[:date] = post[:date] ? Time.parse(post[:date]) : Time.new.getgm
38
+
39
+ post[:body] = ""
40
+ while (line = @socket.readline)
41
+ break if line.match(/^\.$/)
42
+ post[:body] << line
43
+ end
44
+
45
+ @id = post[:id]
46
+ @date = post[:date]
47
+ @author = post[:from]
48
+ @body = post[:body]
49
+ @authority = post[:authority]
50
+ return post
51
+ end
52
+
53
+ # Checks to see if the currently-logged-in user has permission to delete the current post.
54
+ #
55
+ # Returns true or false
56
+ def delete?
57
+ @socket.puts("OKAY DELETE NOTE #{self.id}")
58
+ response = @socket.readline.chomp
59
+ if response.match(/^2/)
60
+ return true
61
+ elsif response.match(/^4/)
62
+ return false
63
+ else
64
+ raise ResponseError, response
65
+ end
66
+ end
67
+
68
+ # Attempts to delete the current post.
69
+ #
70
+ # Returns true or false
71
+ def delete
72
+ @socket.puts("DELETE NOTE #{self.id}")
73
+ response = @socket.readline.chomp
74
+ if response.match(/^2/)
75
+ return true
76
+ elsif response.match(/^4/)
77
+ return false
78
+ else
79
+ raise ResponseError, response
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,58 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{raccdoc}
8
+ s.version = "0.0.10"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["H. Wade Minter"]
12
+ s.date = %q{2009-10-08}
13
+ s.description = %q{A Ruby way to speak the Raccdoc protocol}
14
+ s.email = %q{minter@lunenburg.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".gitignore",
21
+ "CHANGELOG",
22
+ "LICENSE",
23
+ "Manifest",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "lib/raccdoc.rb",
28
+ "lib/raccdoc/connection.rb",
29
+ "lib/raccdoc/forum.rb",
30
+ "lib/raccdoc/post.rb",
31
+ "raccdoc.gemspec",
32
+ "test/raccdoc_connection_test.rb",
33
+ "test/raccdoc_forum_test.rb",
34
+ "test/raccdoc_post_test.rb",
35
+ "test/test_helper.rb"
36
+ ]
37
+ s.homepage = %q{http://github.com/minter/raccdoc}
38
+ s.rdoc_options = ["--charset=UTF-8"]
39
+ s.require_paths = ["lib"]
40
+ s.rubygems_version = %q{1.3.5}
41
+ s.summary = %q{Ruby interface into the Raccdoc BBS API}
42
+ s.test_files = [
43
+ "test/raccdoc_connection_test.rb",
44
+ "test/raccdoc_forum_test.rb",
45
+ "test/raccdoc_post_test.rb",
46
+ "test/test_helper.rb"
47
+ ]
48
+
49
+ if s.respond_to? :specification_version then
50
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
51
+ s.specification_version = 3
52
+
53
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
54
+ else
55
+ end
56
+ else
57
+ end
58
+ end
@@ -0,0 +1,89 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocConnectionTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.expects(:readline).returns("200 Server started seed:Ci0B2YeMio8ftYjElTa+EQ==")
8
+ TCPSocket.expects(:new).returns(@socket)
9
+ @bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
10
+ end
11
+
12
+ def test_valid_anonymous_connection
13
+ assert_equal @bbs.class, Raccdoc::Connection
14
+ assert_equal @bbs.host, "test.bbs.example"
15
+ end
16
+
17
+ def test_connection_refused_anonymous
18
+ TCPSocket.expects(:new).with('test.bbs.example', '6145').raises(Errno::ECONNREFUSED)
19
+ assert_raise(ConnectionError, 'Could not connect to test.bbs.example, port 6145') do
20
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
21
+ end
22
+ end
23
+
24
+ def test_connection_bad_login
25
+ socket = mock()
26
+ socket.stubs(:readline).returns("200 Server started seed:Ci0B2YeMio8ftYjElTa+EQ==").then.returns("405 Cannot process login Invalid password")
27
+ socket.stubs(:puts)
28
+ TCPSocket.expects(:new).returns(socket)
29
+ assert_raise(InvalidLogin, '404 User does not exist') do
30
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example', :user => 'Bugcrusher', :password => 'BadPassword')
31
+ end
32
+ end
33
+
34
+ def test_connection_that_returns_unknown_status
35
+ socket = mock()
36
+ TCPSocket.expects(:new).returns(socket)
37
+ socket.expects(:readline).returns("999 Stuff is messed up")
38
+ assert_raise(ConnectionError) do
39
+ bbs = Raccdoc::Connection.new(:host => 'test.bbs.example')
40
+ end
41
+ end
42
+
43
+ def test_logout_succeeds
44
+ @socket.stubs(:close)
45
+ @socket.stubs(:closed?).returns(false)
46
+ @socket.stubs(:readline).returns("201 Session ended")
47
+ result = @bbs.logout
48
+ assert_equal result, true
49
+ end
50
+
51
+ def test_logout_fails
52
+ @socket.stubs(:close)
53
+ @socket.stubs(:closed?).returns(false)
54
+ @socket.stubs(:readline).returns("999 Something Broke")
55
+ assert_raise(ConnectionError) do
56
+ @bbs.logout
57
+ end
58
+ end
59
+
60
+ def test_connected
61
+ @socket.stubs(:puts)
62
+ @socket.stubs(:closed?).returns(false)
63
+ @socket.stubs(:readline).returns("999 Something Broke")
64
+ assert_equal @bbs.connected?, true
65
+ end
66
+
67
+ def test_forums_success
68
+ @socket.stubs(:readline).returns("301 Topic list follows").then.returns("topic:0 name:Lobby lastnote:2331 flags:nosubject,sparse admin:acct578247-oldisca/Elvis/(hidden)").then.returns(".")
69
+ forums = @bbs.forums
70
+ assert_equal forums.class, Hash
71
+ assert_equal forums.size, 1
72
+ assert_equal forums[0][:name], "Lobby"
73
+ end
74
+
75
+ def test_forums_failure
76
+ @socket.stubs(:readline).returns("999 Stuff Broke")
77
+ assert_raise(ResponseError) do
78
+ forums = @bbs.forums
79
+ end
80
+ end
81
+
82
+ def test_jump
83
+ @forum = mock()
84
+ @forum.expects(:name).returns("Lobby")
85
+ Raccdoc::Forum.expects(:new).returns(@forum)
86
+ forum = @bbs.jump(0)
87
+ assert_equal forum.name, "Lobby"
88
+ end
89
+ end
@@ -0,0 +1,167 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocForumTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.stubs(:readline).returns("204 Topic set to: topic:0 name:Lobby lastnote:2331 flags:nosubject,sparse admin:acct578247-oldisca/Elvis/(hidden) firstnote:2275")
8
+ @forum = Raccdoc::Forum.new(@socket, 0)
9
+ end
10
+
11
+ def test_creation
12
+ assert_equal @forum.class, Raccdoc::Forum
13
+ assert_equal @forum.name, "Lobby"
14
+ assert_equal @forum.admin, "Elvis"
15
+ assert_equal @forum.id, "0"
16
+ assert_equal @forum.anonymous, false
17
+ assert_equal @forum.private, false
18
+ end
19
+
20
+ def test_creation_fail
21
+ socket = mock()
22
+ socket.stubs(:puts)
23
+ socket.stubs(:readline).returns('999 Stuff broke')
24
+ assert_raise(ResponseError) do
25
+ forum = Raccdoc::Forum.new(socket,0)
26
+ end
27
+ end
28
+
29
+ def test_anonymous_allowed
30
+ socket = mock()
31
+ socket.stubs(:puts)
32
+ socket.stubs(:readline).returns("204 Topic set to: topic:173 name:Kama Sutra lastnote:57263 flags:nosubject,sparse,private,cananonymous admin:acct30177-oldisca/Kubla Khan/(hidden) firstnote:57111")
33
+ forum = Raccdoc::Forum.new(socket, 173)
34
+ assert_equal forum.anonymous, true
35
+ end
36
+
37
+ def test_anonymous_forced
38
+ socket = mock()
39
+ socket.stubs(:puts)
40
+ socket.stubs(:readline).returns("204 Topic set to: topic:7 name:Weird Soup lastnote:864957 flags:nosubject,sparse,forceanonymous admin:acct576222-oldisca/Weird FM/(hidden) firstnote:864808")
41
+ forum = Raccdoc::Forum.new(socket,7)
42
+ assert_equal forum.anonymous, "force"
43
+ end
44
+
45
+ def test_noteids
46
+ @socket.stubs(:readline).returns("306 Note headers follow").then.returns("noteno:2275").then.returns("noteno:2280").then.returns(".")
47
+ notes = @forum.noteids
48
+ assert_equal notes.class, Array
49
+ assert_equal notes.size, 2
50
+ end
51
+
52
+ def test_noteids_failure
53
+ @socket.stubs(:readline).returns("999 Stuff Broke")
54
+ assert_raise(ResponseError) do
55
+ notes = @forum.noteids
56
+ end
57
+ end
58
+
59
+ def test_forum_information
60
+ @socket.stubs(:readline).returns("303 Topic info follows").then.returns("From: Adonis").then.returns("Date: Fri, 30 May 2003 02:05:00 GMT").then.returns("").then.returns("This forum is where the Sysops post administrative announcements...").then.returns(".")
61
+ fi = @forum.forum_information
62
+ assert_equal fi[:from], "Adonis"
63
+ end
64
+
65
+ def test_forum_information_failure
66
+ @socket.stubs(:readline).returns("999 Stuff broke")
67
+ assert_raise(ResponseError) do
68
+ fi = @forum.forum_information
69
+ end
70
+ end
71
+
72
+ def test_set_first_unread
73
+ @socket.stubs(:readline).returns('206 Value set')
74
+ @forum.first_unread=2139
75
+ end
76
+
77
+ def test_set_first_unread_fails
78
+ @socket.stubs(:readline).returns('999 Stuff Broke')
79
+ assert_raises(ResponseError) do
80
+ @forum.first_unread=99999
81
+ end
82
+ end
83
+
84
+ def test_first_unread
85
+ @socket.stubs(:readline).returns("207 RC Value is: 2281")
86
+ first = @forum.first_unread
87
+ assert_equal first, "2281"
88
+ end
89
+
90
+ def test_first_unread_fails
91
+ @socket.stubs(:readline).returns("999 Stuff Broke")
92
+ assert_raises(ResponseError) do
93
+ first = @forum.first_unread
94
+ end
95
+ end
96
+
97
+ def test_post_headers
98
+ @socket.stubs(:readline).returns("306 Note headers follow").then.returns("noteno:2275 formal-author:acct89144-oldisca/Devil Lady/(hidden) date:Sat, 16 Jun 2007 00:11:00 GMT subject:Have you ever wanted to give a little back? ISCA is size:670").then.returns("noteno:2280 formal-author:acct493910-oldisca/Bleeding Me/(hidden) date:Sat, 13 Oct 2007 18:28:00 GMT subject:Do you have an idea for a permanent forum but aren't sure size:465").then.returns(".")
99
+ headers = @forum.post_headers
100
+ assert_equal headers.class, Hash
101
+ assert_equal headers.size, 2
102
+ post = headers['2275']
103
+ assert_equal post.class, Hash
104
+ assert_equal post[:author], 'Devil Lady'
105
+ assert_equal post[:size], '670'
106
+ assert_equal post[:noteno], '2275'
107
+ end
108
+
109
+ def test_post_headers_fail
110
+ @socket.stubs(:readline).returns("999 Stuff broke")
111
+ assert_raise(ResponseError) do
112
+ headers = @forum.post_headers
113
+ end
114
+ end
115
+
116
+ def test_read
117
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
118
+ post = @forum.read("9999")
119
+ assert_equal post.class, Raccdoc::Post
120
+ end
121
+
122
+ def test_read_dammit
123
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
124
+ post = @forum.read!("9999")
125
+ assert_equal post.class, Raccdoc::Post
126
+ end
127
+
128
+ def test_create_post
129
+ @socket.stubs(:readline).returns('350 Send note body maxsize:50000').then.returns('205 Note posted as note: 71508').then.returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("This is a test").then.returns(".")
130
+ post = @forum.post("This is a test")
131
+ assert_equal post.class, Raccdoc::Post
132
+ assert_equal post.body, "This is a test"
133
+ end
134
+
135
+ def test_create_post_early_failure
136
+ @socket.stubs(:readline).returns('999 Stuff broke')
137
+ assert_raise(ResponseError) do
138
+ post = @forum.post("This is a test")
139
+ end
140
+ end
141
+
142
+ def test_create_post_late_failure
143
+ @socket.stubs(:readline).returns('350 Send note body maxsize:50000').then.returns('999 Stuff broke')
144
+ assert_raise(ResponseError) do
145
+ post = @forum.post("This is a test")
146
+ end
147
+ end
148
+
149
+ def test_okay_post
150
+ @socket.stubs(:readline).returns("211 Request okay")
151
+ assert_equal @forum.post?, true
152
+ end
153
+
154
+ def test_okay_post_denied
155
+ @socket.stubs(:readline).returns("408 Permission denied")
156
+ assert_equal @forum.post?, false
157
+ end
158
+
159
+ def test_okay_post_fail
160
+ @socket.stubs(:readline).returns("999 Stuff broke")
161
+ assert_raise(ResponseError) do
162
+ @forum.post?
163
+ end
164
+ end
165
+
166
+
167
+ end
@@ -0,0 +1,61 @@
1
+ require File.dirname(__FILE__) + '/test_helper'
2
+
3
+ class RaccdocPostTest < Test::Unit::TestCase
4
+ def setup
5
+ @socket = mock()
6
+ @socket.stubs(:puts)
7
+ @socket.stubs(:readline).returns("302 Note body follows noteno:71493 size:66").then.returns("From: Lunenburg").then.returns("Formal-Name: acct123507-oldisca/Lunenburg/(hidden)").then.returns("Date: Wed, 06 Jun 2007 01:02:00 GMT").then.returns("").then.returns("Testing to see if posting via the ISCAweb interface still works.").then.returns(".")
8
+ @post = Raccdoc::Post.new(@socket, 9999)
9
+ end
10
+
11
+ def test_successful_post_object_creation
12
+ assert_equal @post.class, Raccdoc::Post
13
+ end
14
+
15
+ def test_post_object_creation_failure
16
+ @socket.stubs(:readline).returns("410 No Such Note")
17
+ assert_raise(ResponseError) do
18
+ @post = Raccdoc::Post.new(@socket, 9999)
19
+ end
20
+ end
21
+
22
+ def test_delete_post
23
+ @socket.stubs(:readline).returns("208 Note deleted")
24
+ status = @post.delete
25
+ assert_equal status, true
26
+ end
27
+
28
+ def test_delete_failure
29
+ @socket.stubs(:readline).returns("408 Permission denied")
30
+ status = @post.delete
31
+ assert_equal status, false
32
+ end
33
+
34
+ def test_delete_exception
35
+ @socket.stubs(:readline).returns("999 Stuff Broke")
36
+ assert_raise(ResponseError) do
37
+ status = @post.delete
38
+ end
39
+ end
40
+
41
+ def test_delete_permissions
42
+ @socket.stubs(:readline).returns("211 Request okay")
43
+ status = @post.delete?
44
+ assert_equal status, true
45
+ end
46
+
47
+ def test_delete_permissions_failure
48
+ @socket.stubs(:readline).returns("408 Permission denied")
49
+ status = @post.delete?
50
+ assert_equal status, false
51
+ end
52
+
53
+ def test_delete_permissions_exception
54
+ @socket.stubs(:readline).returns("999 Stuff broke")
55
+ assert_raise(ResponseError) do
56
+ status = @post.delete?
57
+ end
58
+ end
59
+
60
+
61
+ end
@@ -0,0 +1,5 @@
1
+ require 'test/unit'
2
+ $:.unshift File.dirname(__FILE__) + '/../lib'
3
+ require 'raccdoc'
4
+ require 'rubygems'
5
+ require 'mocha'
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: raccdoc
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.10
5
+ platform: ruby
6
+ authors:
7
+ - H. Wade Minter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-08 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A Ruby way to speak the Raccdoc protocol
17
+ email: minter@lunenburg.org
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README.rdoc
25
+ files:
26
+ - .gitignore
27
+ - CHANGELOG
28
+ - LICENSE
29
+ - Manifest
30
+ - README.rdoc
31
+ - Rakefile
32
+ - VERSION
33
+ - lib/raccdoc.rb
34
+ - lib/raccdoc/connection.rb
35
+ - lib/raccdoc/forum.rb
36
+ - lib/raccdoc/post.rb
37
+ - raccdoc.gemspec
38
+ - test/raccdoc_connection_test.rb
39
+ - test/raccdoc_forum_test.rb
40
+ - test/raccdoc_post_test.rb
41
+ - test/test_helper.rb
42
+ has_rdoc: true
43
+ homepage: http://github.com/minter/raccdoc
44
+ licenses: []
45
+
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --charset=UTF-8
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ required_rubygems_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: "0"
62
+ version:
63
+ requirements: []
64
+
65
+ rubyforge_project:
66
+ rubygems_version: 1.3.5
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Ruby interface into the Raccdoc BBS API
70
+ test_files:
71
+ - test/raccdoc_connection_test.rb
72
+ - test/raccdoc_forum_test.rb
73
+ - test/raccdoc_post_test.rb
74
+ - test/test_helper.rb