Skip to content

Instantly share code, notes, and snippets.

@eviltrout
Created December 19, 2012 15:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eviltrout/4337577 to your computer and use it in GitHub Desktop.
Save eviltrout/4337577 to your computer and use it in GitHub Desktop.
NNTP Connection Class in Ruby
#
# Example usage:
#
# require 'rubygems'
# require 'date'
# require 'socket'
# require 'celluloid'
# require 'nntp_connection'
#
# nntp = NNTPConnection.new('news.supernews.com', 119, 'username', 'password')
# group = nntp.group('alt.binaries.boneless')
#
# nntp.xover(group.high - 50, group.high)
# ==> articles returned!
require 'iconv'
class NNTPGroup
attr_reader :name, :count, :low, :high
def initialize(name, count, low, high)
@name = name
@count = count
@low = low
@high = high
end
end
class NNTPOverview
attr_accessor :articles, :group, :low, :high
def initialize(response)
@articles = []
@low = nil
@high = nil
response.split("\n").each do |row|
fields = row.strip.split("\t")
if !fields.empty?
number, subject, author, created_at, message_id = fields
@low = number if @low.nil? or number < low
@high = number if @high.nil? or number > high
@articles << NNTPArticle.new(number, subject, author, created_at, message_id)
end
end
end
end
class NNTPArticle
attr_accessor :number, :subject, :author, :created_at, :message_id
def initialize(number, subject, author, created_at, message_id)
@number = number
ic = Iconv.new('UTF-8//IGNORE', 'UTF-8')
@subject = ic.iconv(subject + ' ')[0..-2]
@author = ic.iconv(author + ' ')[0..-2]
@created_at = DateTime.parse(created_at)
@message_id = message_id
end
end
class NNTPConnection
include Celluloid::Logger
def initialize(host, port, username, password)
@host = host
@port = port
info "Opening connection to #{host}:#{port}"
@socket = TCPSocket.new @host, @port
recv_response
# Authentifcate
command("AUTHINFO USER #{username}", true)
command("AUTHINFO PASS #{password}")
end
def recv_response(allow_continue=false)
stat = ''
while true
line = @socket.readline
stat << line << "\n"
break unless line[3] == ?- # "210-PIPELINING"
end
return stat if /\A1/ === stat # 1xx info msg
return stat if /\A2/ === stat # 2xx cmd k
return stat if allow_continue and /\A[35]/ === stat # 3xx cmd k, snd rst
raise "NNTP Error: #{stat}"
end
def command(cmd, allow_continue=false)
@socket.puts(cmd)
recv_response(allow_continue)
end
def group(name)
result = command("GROUP #{name}")
chunks = result.split
NNTPGroup.new(chunks[4], chunks[1].to_i, chunks[2].to_i, chunks[3].to_i)
end
def multiline_command(cmd, *args)
result = ""
if stat = command(cmd, *args)
while true
line = @socket.readline
break if line =~ /^\.\s*$/
line = line[1..-1] if line.to_s[0...2] == '..'
result << line + $/
end
end
return result
end
def xover(low, high)
response = multiline_command("XOVER #{low}-#{high}")
NNTPOverview.new(response)
end
def capabilities
multiline_command('CAPABILITIES')
end
def close
@socket.close
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment