From: Yohanes Santoso Date: 2002-05-07T18:16:04+09:00 Subject: Re: NNTP library for Ruby? --=-=-= nosuzuki@postcard.st (Norio Suzuki) writes: > Hi Usano! > > > From: Usano > > Date: Tue, 7 May 2002 04:34:24 +0900 > >> Message-ID: > > > Is it easy to use Ruby to access Usenet? > > There is one library to access NNTP server on the RAA. Have you tried > this? http://www.ruby-lang.org/en/raa-list.rhtml?name=net%2Fnntp > > I am not using this but it looks easy to use. Please try it. I evaluated that package before, but it _WAS_ (6 months ago): 1. not very ruby-ish 2. somewhat slower than the other nntp package at http://www02.so-net.ne.jp/~greentea/ruby/nntp/. In the end I used the one from greentea. I made a few modification both for performance and protocol update reasons: faster, simpler line get, and auth and xover supports. The documentation of the package is admitedly lacking, but that was not a significant problem. Here is my patch for the package if you decide to use it: --=-=-= Content-Type: text/x-patch Content-Disposition: attachment; filename=ruby-nntp.patch.gz Content-Transfer-Encoding: 8bit diff -Naur ruby-nntp.orig/nntp.rb ruby-nntp/nntp.rb --- ruby-nntp.orig/nntp.rb Tue May 7 08:58:37 2002 +++ ruby-nntp/nntp.rb Tue May 7 09:13:00 2002 @@ -1,9 +1,10 @@ -# $Id: nntp.rb,v 2.14 2000/02/26 17:28:53 rgt Exp $ +# $Id: ruby_nntp.rb,v 1.2 2001/12/23 14:53:44 ysantoso Exp $ -require 'nntp/nntp_h' -require 'nntp/nntptime' +#modified from http://www02.so-net.ne.jp/~greentea/ruby/nntp/ + +require 'nntp_h' +require 'nntptime' require 'socket' -require 'tsdreader' module NNTP class NNTPIO @@ -17,9 +18,9 @@ #raise NNTPError (when error in NNTP) or StandardError (when error in methods of TCPSocket) =end - def NNTPIO.session(host, port=WELL_KNOWN_PORT, &block) + def NNTPIO.session(host, port=WELL_KNOWN_PORT, user = nil, pass = nil, &block) nntp = NNTPIO.new(host, port) - nntp.connect + nntp.connect(user, pass) begin block.call nntp ensure @@ -27,6 +28,7 @@ end end + def initialize(host, port=WELL_KNOWN_PORT) ## host.is_a? String and port.is_a? Integer @host = host @@ -35,6 +37,7 @@ @response = nil @socket = nil @postable = false + puts "inited" end attr_reader :response, :postable @@ -43,20 +46,93 @@ #raise NNTPError (when error in NNTP) or StandardError (when error in methods of TCPSocket) =end - def connect + def connect(user=nil, pass=nil) + puts "entring connect" if not closed? return end + puts "host=#{@host}, port=#{@port}" @socket = open_socket(@host, @port) resp = get_response if resp[0..2] == OK_READY + authenticate(user, pass) if user return (@postable = true) elsif resp[0..2] == OK_READY_NO_POST + authenticate(user, pass) if user return (@postable = false) else raise NNTPError.new(resp) end end + +=begin + #added by YS (2001-Nov-24) + #raise NNTPError if fails +=end + def authenticate(user, pass) + req = AUTHINFO_USER+" #{user}" + resp = send_command req + if resp[0..2] == PASS_REQUIRED + resp = send_command AUTHINFO_PASS+" #{pass}" + if resp[0..2] == OK_READY + return + end + else + raise NNTPError.new(resp) + end + end + +=begin + # added by YS (2001-Nov-25) + #raise NNTPError if fails +=end + def mode_reader + resp = send_command MODE_READER + if resp[0..2] == OK_READY + return + else + raise NNTPError.new(resp) + end + end + +=begin + #added by YS (2001-Nov-25) + #raise NNTPError if fails + #similar to get_newgroups +=end + def get_xover(artRange) + resp = send_command XOVER+" #{artRange.first}-#{artRange.last}" + if resp[0..2] != OK_OVERVIEW + raise NNTPError(resp) + end + while (line = @socket.gets(CRLF)) + if eom? line + break + else + yield make_xover_info(line) + end + end + end + +=begin + # added by YS (2001 Nov 25) + # similar to make_group_info + #return [artnum, subject, author, date, msgId, + # references, bytes, lines, other junks that may or may not present] + #raise NNTPError (when error in NNTP) or StandardError (when error in methods + of TCPSocket) +=end + def make_xover_info(line) + ## line.is_a? String + arry = line.chomp(CRLF).split("\t", 9) + if arry.size >= 8 + return arry + else + return nil + end + end + + =begin #return boolean @@ -65,6 +141,7 @@ @socket.nil? or @socket.closed? end + =begin #return TCPSocket #raise StandardError when error in TCPSocket#new @@ -91,7 +168,7 @@ if resp[0..2] != OK_ARTICLE raise NNTPError.new(resp) end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom?(line) break else @@ -119,7 +196,7 @@ if resp[0..2] != OK_BODY raise NNTPError.new(resp) end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom?(line) break else @@ -148,7 +225,7 @@ raise NNTPError.new(resp) end line = nil - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom?(line) break else @@ -241,7 +318,7 @@ if resp[0..2] != INFO_HELP raise NNTPError, resp end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom? line break else @@ -269,12 +346,17 @@ #raise NNTPError (when error in NNTP) or StandardError (when error in methods of TCPSocket) =end - def get_list(&block) + ## YS 2001-12-23: added option group_pattern. + def get_list(group_pattern = nil, &block) ## block != null - if send_command(LIST)[0..2] != OK_LIST + req = LIST + if group_pattern + req += " active #{group_pattern}" + end + if send_command(req)[0..2] != OK_LIST raise NNTPError, resp end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom? line break else @@ -300,7 +382,7 @@ if (resp = send_command(req))[0..2] != OK_NEWGROUPS raise NNTPError.new(resp) end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom? line break else @@ -310,7 +392,7 @@ end =begin - #return [group_name, last_article_number, first_article_number, post_allowed] + #return [group_name, last_article_number, first_article_number, post_allowed] #raise NNTPError (when error in NNTP) or StandardError (when error in methods of TCPSocket) =end @@ -362,7 +444,7 @@ if resp[0..2] != OK_NEWNEWS raise NNTPError.new(resp) end - while (line = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) + while (line = @socket.gets(CRLF)) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF)) if eom? line break else @@ -496,7 +578,7 @@ of TCPSocket) =end def get_response - resp = TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF) + resp = @socket.gets(CRLF) #TSDReader.read_line(@socket, TSDReader::BUFSIZ, CRLF) if resp[0] == ?5 or resp[0..2] == FAIL_SERVICE raise NNTPError.new(resp) @@ -507,5 +589,5 @@ class NNTPError < StandardError end - + end diff -Naur ruby-nntp.orig/nntp_h.rb ruby-nntp/nntp_h.rb --- ruby-nntp.orig/nntp_h.rb Tue May 7 08:58:52 2002 +++ ruby-nntp/nntp_h.rb Tue May 7 09:00:28 2002 @@ -15,7 +15,16 @@ POST = 'POST'.freeze QUIT = 'QUIT'.freeze SLAVE = 'SLAVE'.freeze - + + AUTHINFO_USER = 'AUTHINFO USER'.freeze + AUTHINFO_PASS = 'AUTHINFO PASS'.freeze + MODE_READER = 'MODE READER'.freeze + LIST_OVER = 'LIST OVERVIEW.FMT'.freeze + XOVER = 'XOVER'.freeze + OK_OVERVIEW = '224'.freeze # overview command succeed + OK_LISTOVER = '215'.freeze # list overview command succeed. + + # NNTP responses INFO_HELP = '100'.freeze # help text follows INFO_DEBUG = '199'.freeze # debug output @@ -35,6 +44,7 @@ OK_IHAVE = '235'.freeze # article transferred ok OK_POST = '240'.freeze # article posted ok + PASS_REQUIRED = '381'.freeze # want to have pass. SEND_IHAVE = '335'.freeze # send article to be transferred. SEND_POST = '340'.freeze # send article to be posted. @@ -54,6 +64,6 @@ NOT_RECOGNIZED = '500'.freeze # command not recognized SYNTAX_ERROR = '501'.freeze # command syntax error - PERMISSION_DENINED = '502'.freeze # access restriction or permission denied + PERMISSION_DENIED = '502'.freeze # access restriction or permission denied FAULT = '503'.freeze # program fault - command not performed end diff -Naur ruby-nntp.orig/nntptime.rb ruby-nntp/nntptime.rb --- ruby-nntp.orig/nntptime.rb Tue May 7 08:58:45 2002 +++ ruby-nntp/nntptime.rb Tue May 7 09:00:28 2002 @@ -1,12 +1,13 @@ module NNTP module NNTPTime =begin + ## modified by YS. Year should be a 4-digit string (new standard). ##pre time.is_a? Time or time.is_a? SlowTime ##post const time ##return Time#to_s =end def NNTPTime.time2str(time) - time.dup.gmtime.strftime '%y%m%d %H%M%S GMT' + time.dup.gmtime.strftime '%Y%m%d %H%M%S GMT' end =begin diff -Naur ruby-nntp.orig/test-nntp.rb ruby-nntp/test-nntp.rb --- ruby-nntp.orig/test-nntp.rb Tue May 7 08:58:59 2002 +++ ruby-nntp/test-nntp.rb Tue May 7 09:11:22 2002 @@ -3,7 +3,8 @@ include NNTP now = Time.now.gmtime -yest = Time.at(now.to_i - 60 * 60 * 24) +yest = Time.at(now.to_i - 60 * 60 * 24 * 30*12*10) +puts yest nntp = NNTPIO.new(ARGV[0]) @@ -27,10 +28,11 @@ puts 'done get_list' =end print 'distro: ' -dists = $stdin.gets.chomp.split -if dists.empty? - dists = nil -end +#dists = $stdin.gets.chomp.split +#if dists.empty? +# dists = nil +#end +dists=nil # NEWGROUPS puts 'do get_newgroups' @@ -42,8 +44,9 @@ puts 'done get_newgroups' # GROUP, NEWNEWS -print 'group: ' -grp = $stdin.gets.chomp +#print 'group: ' +#grp = $stdin.gets.chomp +grp = nil p nntp.set_group(grp) puts 'done set_group' @@ -56,8 +59,9 @@ end puts 'done get_newnews' -print 'article number: ' -an = $stdin.gets.chomp +#print 'article number: ' +#an = $stdin.gets.chomp +grp=nil # STAT, LAST, NEXT puts 'do set_stat, set_last, set_next' diff -Naur ruby-nntp.orig/tsdreader.rb ruby-nntp/tsdreader.rb --- ruby-nntp.orig/tsdreader.rb Tue May 7 08:59:05 2002 +++ ruby-nntp/tsdreader.rb Thu Jan 1 00:00:00 1970 @@ -1,60 +0,0 @@ -module TSDReader - BUFSIZ = 8192 - TSD_COUNT = :_read_char_count - TSD_BUFF = :_read_char_buff - - ## mostly private - def TSDReader.read_char(io) - str = result = nil - if not Thread.current[TSD_COUNT] or - Thread.current[TSD_COUNT] == 0 - begin - str = io.sysread(BUFSIZ) - rescue Errno::EINTR - retry - rescue EOFError - if str.nil? or str.size == 0 - Thread.current[TSD_BUFF] = nil - Thread.current[TSD_COUNT] = 0 - return -1 - end - end - Thread.current[TSD_BUFF] = str - Thread.current[TSD_COUNT] = str.size - end - result = Thread.current[TSD_BUFF][Thread.current[TSD_BUFF].size - - Thread.current[TSD_COUNT]] - Thread.current[TSD_COUNT] -= 1 - result - end - - ##pre io.is_a? IO and maxlen.is_a? Integer and rs.is_a? String - def TSDReader.read_line(io, maxlen=BUFSIZ, rs=$/) - line = '' - for i in 1..maxlen - c = read_char(io) - if c < 0 - if line.size == 0 - return nil - else - break - end - end - line << c.chr - if line.index(rs, -(rs.size)) - break - end - end - if i == maxlen and not line.index(rs, -(rs.size)) - raise IOError, 'too long line' - end - line - end -end - -if $0 == __FILE__ - io = File.open(ARGV[0]) - while (line = TSDReader.read_line(io, 1024)) - puts line - end -end --=-=-= Hope this helps. YS. --=-=-=--