net-irc-0.0.9/ 0000775 0001750 0001750 00000000000 11604616526 012726 5 ustar uwabami uwabami net-irc-0.0.9/examples/ 0000775 0001750 0001750 00000000000 11604616526 014544 5 ustar uwabami uwabami net-irc-0.0.9/examples/2ig.rb 0000755 0001750 0001750 00000013467 11604616526 015566 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" if RUBY_VERSION < "1.9" # json use this
require "rubygems"
require "net/irc"
require "logger"
require "pathname"
require "yaml"
require 'uri'
require 'net/http'
require 'nkf'
require 'stringio'
require 'zlib'
require "#{Pathname.new(__FILE__).parent.expand_path}/2ch.rb"
Net::HTTP.version_1_2
class NiChannelIrcGateway < Net::IRC::Server::Session
def server_name
"2ch"
end
def server_version
"0.0.0"
end
def initialize(*args)
super
@channels = {}
end
def on_disconnected
@channels.each do |chan, info|
begin
info[:observer].kill if info[:observer]
rescue
end
end
end
def on_user(m)
super
@real, *@opts = @real.split(/\s+/)
@opts ||= []
end
def on_join(m)
channels = m.params.first.split(/,/)
channels.each do |channel|
@channels[channel] = {
:topic => "",
:dat => nil,
:interval => nil,
:observer => nil,
} unless @channels.key?(channel)
post @prefix, JOIN, channel
post nil, RPL_NAMREPLY, @prefix.nick, "=", channel, "@#{@prefix.nick}"
post nil, RPL_ENDOFNAMES, @prefix.nick, channel, "End of NAMES list"
end
end
def on_part(m)
channel = m.params[0]
if @channels.key?(channel)
info = @channels.delete(channel)
info[:observer].kill if info[:observer]
post @prefix, PART, channel
end
end
def on_privmsg(m)
target, mesg = *m.params
m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
end
def on_ctcp(target, mesg)
type, mesg = mesg.split(" ", 2)
method = "on_ctcp_#{type.downcase}".to_sym
send(method, target, mesg) if respond_to? method, true
end
def on_ctcp_action(target, mesg)
command, *args = mesg.split(" ")
command.downcase!
case command
when 'next'
if @channels.key?(target)
guess_next_thread(target)
end
end
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
def on_topic(m)
channel, topic, = m.params
p m.params
if @channels.key?(channel)
info = @channels[channel]
unless topic
post nil, '332', channel, info[:topic]
return
end
uri, interval = *topic.split(/\s/)
interval = interval.to_i
post @prefix, TOPIC, channel, topic
case
when !info[:dat], uri != info[:dat].uri
post @prefix, NOTICE, channel, "Thread URL has been changed."
info[:dat] = ThreadData.new(uri)
create_observer(channel)
when info[:interval] != interval
post @prefix, NOTICE, channel, "Interval has been changed."
create_observer(channel)
end
info[:topic] = topic
info[:interval] = interval > 0 ? interval : 90
end
end
def guess_next_thread(channel)
info = @channels[channel]
post server_name, NOTICE, channel, "Current Thread: #{info[:dat].subject}"
threads = info[:dat].guess_next_thread
threads.first(3).each do |t|
if t[:continuous_num] && t[:appear_recent]
post server_name, NOTICE, channel, "#{t[:uri]} \003%d#{t[:subject]}\017" % 10
else
post server_name, NOTICE, channel, "#{t[:uri]} #{t[:subject]}"
end
end
threads
end
def create_observer(channel)
info = @channels[channel]
info[:observer].kill if info[:observer]
@log.debug "create_observer %s, interval %d" % [channel, info[:interval]]
info[:observer] = Thread.start(info, channel) do |info, channel|
Thread.pass
loop do
begin
sleep info[:interval]
@log.debug "retrieving (interval %d) %s..." % [info[:interval], info[:dat].uri]
info[:dat].retrieve.last(100).each do |line|
priv_line channel, line
end
if info[:dat].length >= 1000
post server_name, NOTICE, channel, "Thread is over 1000. Guessing next thread..."
guess_next_thread(channel)
break
end
rescue UnknownThread
# pass
rescue Exception => e
@log.error "Error: #{e.inspect}"
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
end
end
end
def priv_line(channel, line)
post "%d{%s}" % [line.n, line.id], PRIVMSG, channel, line.aa?? encode_aa(line.body) : line.body
end
def encode_aa(aa)
uri = URI('http://tinyurl.com/api-create.php')
uri.query = 'url=' + URI.escape(<<-EOS.gsub(/[\n\t]/, ''))
data:text/html,
#{aa.gsub(/\n/, ' ')}
EOS
Net::HTTP.get(uri.host, uri.request_uri, uri.port)
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16701,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
trap("SIGINT") { exit! 0 }
trap("SIGTERM") { exit! 0 }
trap("SIGHUP") { exit! 0 }
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f
STDERR.reopen f
}
yield
end
exit! 0
end
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], NiChannelIrcGateway, opts).start
end
end
net-irc-0.0.9/examples/sig.rb 0000755 0001750 0001750 00000007217 11604616526 015663 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
=begin
# sig.rb
ServerLog IRC Gateway
# Usage
* Connect.
* Join a channel (you can name it as you like)
* Set topic "filename regexp"
* You will see the log at the channel only matching the regexp.
=end
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding
require "rubygems"
require "net/irc"
require "logger"
require "pathname"
require "yaml"
class ServerLogIrcGateway < Net::IRC::Server::Session
def server_name
"serverlog"
end
def server_version
"0.0.0"
end
def initialize(*args)
super
@channels = {}
@config = Pathname.new(ENV["HOME"]) + ".sig"
end
def on_disconnected
@channels.each do |chan, info|
begin
info[:observer].kill if info[:observer]
rescue
end
end
end
def on_user(m)
super
@real, *@opts = @real.split(/\s+/)
@opts ||= []
end
def on_join(m)
channels = m.params.first.split(/,/)
channels.each do |channel|
@channels[channel] = {
:topic => "",
:observer => nil,
} unless @channels.key?(channel)
post @prefix, JOIN, channel
post nil, RPL_NAMREPLY, @prefix.nick, "=", channel, "@#{@prefix.nick}"
post nil, RPL_ENDOFNAMES, @prefix.nick, channel, "End of NAMES list"
end
end
def on_topic(m)
channel, topic, = m.params
p m.params
if @channels.key?(channel)
post @prefix, TOPIC, channel, topic
@channels[channel][:topic] = topic
create_observer(channel)
end
end
def create_observer(channel)
return unless @channels.key?(channel)
info = @channels[channel]
@log.debug "create_observer<#{channel}>#{info.inspect}"
begin
info[:observer].kill if info[:observer]
rescue
end
info[:observer] = Thread.start(channel, info) do |chan, i|
file, reg, = i[:topic].split(/\s+/)
name = File.basename(file)
grep = Regexp.new(reg.to_s)
@log.info "#{file} / grep => #{grep.inspect}"
File.open(file) do |f|
size = File.size(f)
f.seek size
loop do
sleep 1
nsize = File.size(f)
if nsize > size
@log.debug "follow up log"
while l = f.gets
if grep === l
post name, PRIVMSG, chan, l
end
end
end
size = nsize
end
end
end
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16700,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
trap("SIGINT") { exit! 0 }
trap("SIGTERM") { exit! 0 }
trap("SIGHUP") { exit! 0 }
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f
STDERR.reopen f
}
yield
end
exit! 0
end
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], ServerLogIrcGateway, opts).start
end
end
net-irc-0.0.9/examples/lig.rb 0000755 0001750 0001750 00000034447 11604616526 015661 0 ustar uwabami uwabami #!/usr/bin/env ruby
=begin
# lig.rb
Lingr IRC Gateway - IRC Gateway to Lingr ( http://www.lingr.com/ )
## Launch
$ ruby lig.rb # daemonized
If you want to help:
$ ruby lig.rb --help
Usage: examples/lig.rb [opts]
Options:
-p, --port [PORT=16669] port number to listen
-h, --host [HOST=localhost] host name or IP address to listen
-l, --log LOG log file
-a, --api_key API_KEY Your api key on Lingr
--debug Enable debug mode
## Configuration
Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
lingr {
host: localhost
port: 16669
name: username@example.com (Email on Lingr)
password: password on Lingr
in-encoding: utf8
out-encoding: utf8
}
Set your email as IRC 'real name' field, and password as server password.
This does not allow anonymous connection to Lingr.
You must create a account on Lingr and get API key (ask it first time).
## Client
This gateway sends multibyte nicknames at Lingr rooms as-is.
So you should use a client which treats it correctly.
Recommended:
* LimeChat for OSX ( http://limechat.sourceforge.net/ )
* Irssi ( http://irssi.org/ )
* (gateway) Tiarra ( http://coderepos.org/share/wiki/Tiarra )
## Nickname/Mask
nick -> nickname in a room.
o_id -> occupant_id (unique id in a room)
u_id -> user_id (unique user id in Lingr)
* Anonymous User: |!anon@lingr.com
* Logged-in User: |!@lingr.com
* Your: |!@lingr.com
So you can see some nicknames in same user, but it is needed for
nickname management on client.
(Lingr allows different nicknames between rooms in a same user, but IRC not)
## Licence
Ruby's by cho45
## 備考
このクライアントで 1000speakers への応募はできません。lingr.com から行ってください。
=end
$LOAD_PATH << File.dirname(__FILE__)
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "rubygems"
require "lingr"
require "net/irc"
require "pit"
require "mutex_m"
class LingrIrcGateway < Net::IRC::Server::Session
def server_name
"lingrgw"
end
def server_version
"0.0.0"
end
def initialize(*args)
super
@channels = {}
@channels.extend(Mutex_m)
end
def on_user(m)
super
@real, *@copts = @real.split(/\s+/)
@copts ||= []
# Tiarra sends prev nick when reconnects.
@nick.sub!(/\|.+$/, "")
log "Hello #{@nick}, this is Lingr IRC Gateway."
log "Client Option: #{@copts.join(", ")}"
@log.info "Client Option: #{@copts.join(", ")}"
@log.info "Client initialization is completed."
@lingr = Lingr::Client.new(@opts.api_key)
@lingr.create_session('human')
@lingr.login(@real, @pass)
@session_observer = Thread.start do
loop do
begin
@log.info "Verifying session..."
@log.info "Verifed session => #{@lingr.verify_session.inspect}"
rescue Lingr::Client::APIError => e
@log.info "Verify session raised APIError<#{e.code}:#{e.message}>. Try to re-create session."
@lingr.create_session('human')
@lingr.login(@real, @pass)
rescue Exception => e
@log.info "Error on verify_session: #{e.inspect}"
end
sleep 9 * 60
end
end
@user_info = @lingr.get_user_info
prefix = make_ids(@user_info)
@user_info["prefix"] = prefix
post @prefix, NICK, prefix.nick
rescue Lingr::Client::APIError => e
case e.code
when 105
post nil, ERR_PASSWDMISMATCH, @nick, "Password incorrect"
else
log "Error: #{e.code}: #{e.message}"
end
finish
end
def on_privmsg(m)
target, message = *m.params
if @channels.key?(target.downcase)
@lingr.say(@channels[target.downcase][:ticket], message)
else
post nil, ERR_NOSUCHNICK, @user_info["prefix"].nick, target, "No such nick/channel"
end
rescue Lingr::Client::APIError => e
log "Error: #{e.code}: #{e.message}"
log "Coundn't say to #{target}."
on_join(Message.new(nil, "JOIN", [target])) if e.code == 102 # invalid session
end
def on_notice(m)
on_privmsg(m)
end
def on_whois(m)
nick = m.params[0]
chan = nil
info = nil
@channels.each do |k, v|
if v[:users].key?(nick)
chan = k
info = v[:users][nick]
break
end
end
if chan
prefix = info["prefix"]
real_name = info["description"].to_s
server_info = "Lingr: type:#{info["client_type"]} source:#{info["source"]}"
channels = [info["client_type"] == "human" ? "@#{chan}" : chan]
me = @user_info["prefix"]
post nil, RPL_WHOISUSER, me.nick, prefix.nick, prefix.user, prefix.host, "*", real_name
post nil, RPL_WHOISSERVER, me.nick, prefix.nick, prefix.host, server_info
# post nil, RPL_WHOISOPERATOR, me.nick, prefix.nick, "is an IRC operator"
# post nil, RPL_WHOISIDLE, me.nick, prefix.nick, idle, "seconds idle"
post nil, RPL_WHOISCHANNELS, me.nick, prefix.nick, channels.join(" ")
post nil, RPL_ENDOFWHOIS, me.nick, prefix.nick, "End of WHOIS list"
else
post nil, ERR_NOSUCHNICK, me.nick, nick, "No such nick/channel"
end
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
def on_who(m)
channel = m.params[0]
return unless channel
info = @channels.synchronize { @channels[channel.downcase] }
me = @user_info["prefix"]
res = @lingr.get_room_info(info[:chan_id], nil, info[:password])
res["occupants"].each do |o|
next unless o["nickname"]
u_id, o_id, prefix = *make_ids(o, true)
op = (o["client_type"] == "human") ? "@" : ""
post nil, RPL_WHOREPLY, me.nick, channel, o_id, "lingr.com", "lingr.com", prefix.nick, "H*#{op}", "0 #{o["description"].to_s.gsub(/\s+/, " ")}"
end
post nil, RPL_ENDOFWHO, me.nick, channel
rescue Lingr::Client::APIError => e
log "Maybe gateway don't know password for channel #{channel}. Please part and join."
end
def on_join(m)
channels = m.params[0].split(/\s*,\s*/)
password = m.params[1]
channels.each do |channel|
next if @channels.key? channel.downcase
begin
@log.debug "Enter room -> #{channel}"
res = @lingr.enter_room(channel.sub(/^#/, ""), @nick, password)
res["password"] = password
@channels.synchronize do
create_observer(channel, res)
end
rescue Lingr::Client::APIError => e
log "Error: #{e.code}: #{e.message}"
log "Coundn't join to #{channel}."
if e.code == 102
log "Invalid session... prompt the client to reconnect"
finish
end
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
end
end
def on_part(m)
channel = m.params[0]
info = @channels[channel.downcase]
prefix = @user_info["prefix"]
if info
info[:observer].kill
@lingr.exit_room(info[:ticket])
@channels.delete(channel.downcase)
post prefix, PART, channel, "Parted"
else
post nil, ERR_NOSUCHCHANNEL, prefix.nick, channel, "No such channel"
end
rescue Lingr::Client::APIError => e
unless e.code == 102
log "Error: #{e.code}: #{e.message}"
log "Coundn't say to #{target}."
@channels.delete(channel.downcase)
post prefix, PART, channel, "Parted"
end
end
def on_disconnected
@channels.each do |k, info|
info[:observer].kill
end
@session_observer.kill rescue nil
begin
@lingr.destroy_session
rescue
end
end
private
def create_observer(channel, response)
Thread.start(channel, response) do |chan, res|
myprefix = @user_info["prefix"]
if @channels[chan.downcase]
@channels[chan.downcase][:observer].kill rescue nil
end
@channels[chan.downcase] = {
:ticket => res["ticket"],
:counter => res["room"]["counter"],
:o_id => res["occupant_id"],
:chan_id => res["room"]["id"],
:password => res["password"],
:users => res["occupants"].reject {|i| i["nickname"].nil? }.inject({}) {|r,i|
i["prefix"] = make_ids(i)
r.update(i["prefix"].nick => i)
},
:hcounter => 0,
:observer => Thread.current,
}
post server_name, TOPIC, chan, "#{res["room"]["url"]} #{res["room"]["description"]}"
post myprefix, JOIN, channel
post server_name, MODE, channel, "+o", myprefix.nick
post nil, RPL_NAMREPLY, myprefix.nick, "=", chan, @channels[chan.downcase][:users].map{|k,v|
v["client_type"] == "human" ? "@#{k}" : k
}.join(" ")
post nil, RPL_ENDOFNAMES, myprefix.nick, chan, "End of NAMES list"
info = @channels[chan.downcase]
while true
begin
@log.debug "observe_room<#{info[:counter]}><#{chan}> start <- #{myprefix}"
res = @lingr.observe_room info[:ticket], info[:counter]
info[:counter] = res["counter"] if res["counter"]
(res["messages"] || []).each do |m|
next if m["id"].to_i <= info[:hcounter]
u_id, o_id, prefix = *make_ids(m, true)
case m["type"]
when "user"
# Don't send my messages.
unless info[:o_id] == o_id
post prefix, PRIVMSG, chan, m["text"]
end
when "private"
# TODO not sent from lingr?
post prefix, PRIVMSG, chan, ctcp_encoding("ACTION Sent private: #{m["text"]}")
# system:{enter,leave,nickname_changed} should not be used for nick management.
# when "system:enter"
# post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
# when "system:leave"
# post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
# when "system:nickname_change"
# post prefix, PRIVMSG, chan, ctcp_encoding("ACTION #{m["text"]}")
when "system:broadcast"
post "system.broadcast", NOTICE, chan, m["text"]
end
info[:hcounter] = m["id"].to_i if m["id"]
end
if res["occupants"]
enter = [], leave = []
newusers = res["occupants"].reject {|i| i["nickname"].nil? }.inject({}) {|r,i|
i["prefix"] = make_ids(i)
r.update(i["prefix"].nick => i)
}
nickchange = newusers.inject({:new => [], :old => []}) {|r,(k,new)|
old = info[:users].find {|l,old|
# same occupant_id and different nickname
# when nickname was changed and when un-authed user promoted to authed user.
new["prefix"] != old["prefix"] && new["id"] == old["id"]
}
if old
old = old[1]
post old["prefix"], NICK, new["prefix"].nick
r[:old] << old["prefix"].nick
r[:new] << new["prefix"].nick
end
r
}
entered = newusers.keys - info[:users].keys - nickchange[:new]
leaved = info[:users].keys - newusers.keys - entered - nickchange[:old]
leaved.each do |leave|
leave = info[:users][leave]
post leave["prefix"], PART, chan, ""
end
entered.each do |enter|
enter = newusers[enter]
prefix = enter["prefix"]
post prefix, JOIN, chan
if enter["client_type"] == "human"
post server_name, MODE, chan, "+o", prefix.nick
end
end
info[:users] = newusers
end
rescue Lingr::Client::APIError => e
case e.code
when 100
@log.fatal "BUG: API returns invalid HTTP method"
exit 1
when 102
@log.error "BUG: API returns invalid session. Prompt the client to reconnect."
finish
when 104
@log.fatal "BUG: API returns invalid response format. JSON is unsupported?"
exit 1
when 109
@log.error "Error: API returns invalid ticket. Rejoin this channel..."
on_part(Message.new(nil, PART, [chan, res["error"]["message"]]))
on_join(Message.new(nil, JOIN, [chan, info["password"]]))
when 114
@log.fatal "BUG: API returns no counter parameter."
exit 1
when 120
@log.error "Error: API returns invalid encoding. But continues."
when 122
@log.error "Error: API returns repeated counter. But continues."
info[:counter] += 10
log "Error: repeated counter. Some message may be ignored..."
else
# may be socket error?
@log.debug "observe failed : #{res.inspect}"
log "Error: #{e.code}: #{e.message}"
end
rescue Timeout::Error
# pass
rescue JSON::ParserError => e
@log.error e
info[:counter] += 10
log "Error: JSON::ParserError Some message may be ignored..."
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 1
end
end
end
def log(str)
str.gsub!(/\s/, " ")
begin
post nil, NOTICE, @user_info["prefix"].nick, str
rescue
post nil, NOTICE, @nick, str
end
end
def make_ids(o, ext=false)
u_id = o["user_id"] || "anon"
o_id = o["occupant_id"] || o["id"]
nick = (o["default_nickname"] || o["nickname"]).gsub(/\s+/, "")
if o["user_id"] == @user_info["user_id"]
nick << "|#{o["user_id"]}"
else
nick << "|#{o["user_id"] ? o_id : "_"+o_id}"
end
pref = Prefix.new("#{nick}!#{u_id}@lingr.com")
ext ? [u_id, o_id, pref] : pref
end
end
if __FILE__ == $0
require "rubygems"
require "optparse"
require "pit"
opts = {
:port => 16669,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("-a", "--api_key API_KEY", "Your api key on Lingr") do |key|
opts[:api_key] = key
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
[:INT, :TERM, :HUP].each do |sig|
Signal.trap sig, "EXIT"
end
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f, "w"
STDERR.reopen f, "w"
}
yield
end
exit! 0
end
opts[:api_key] = Pit.get("lig.rb", :require => {
"api_key" => "API key of Lingr"
})["api_key"] unless opts[:api_key]
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], LingrIrcGateway, opts).start
end
end
net-irc-0.0.9/examples/lingr.rb 0000644 0001750 0001750 00000023127 11604616526 016207 0 ustar uwabami uwabami # Ruby client for the Lingr[http://www.lingr.com] API. For more details and tutorials, see the
# {Lingr API Reference}[http://wiki.lingr.com/dev/show/API+Reference] pages on the {Lingr Developer Wiki}[http://wiki.lingr.com].
#
# All methods return a hash with two keys:
# * :succeeded - true if the method succeeded, false otherwise
# * :response - a Hash version of the response document received from the server
#
# = api_client.rb
#
# Lingr API client
#
#
# Original written by Lingr.
# Modified by cho45
# * Use json gem instead of gsub/eval.
# * Raise APIError when api fails.
# * Rename class name to Lingr::Client.
$KCODE = 'u' # used by json
require "rubygems"
require "net/http"
require "json"
require "uri"
require "timeout"
module Lingr
class Client
class ClientError < StandardError; end
class APIError < ClientError
def initialize(error)
@error = error || {
"message" => "socket error",
"code" => 0,
}
super(@error["message"])
end
def code
@error["code"]
end
end
attr_accessor :api_key
# 0 = quiet, 1 = some debug info, 2 = more debug info
attr_accessor :verbosity
attr_accessor :session
attr_accessor :timeout
def initialize(api_key, verbosity=0, hostname='www.lingr.com')
@api_key = api_key
@host = hostname
@verbosity = verbosity
@timeout = 60
end
# Create a new API session
#
def create_session(client_type='automaton')
if @session
@error_info = nil
raise ClientError, "already in a session"
end
ret = do_api :post, 'session/create', { :api_key => @api_key, :client_type => client_type }, false
@session = ret["session"]
ret
end
# Verify a session id. If no session id is passed, verifies the current session id for this ApiClient
#
def verify_session(session_id=nil)
do_api :get, 'session/verify', { :session => session_id || @session }, false
end
# Destroy the current API session
#
def destroy_session
ret = do_api :post, 'session/destroy', { :session => @session }
@session = nil
ret
end
# Get a list of the currently hot rooms
#
def get_hot_rooms(count=nil)
do_api :get, 'explore/get_hot_rooms', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
end
# Get a list of the newest rooms
#
def get_new_rooms(count=nil)
do_api :get, 'explore/get_new_rooms', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
end
# Get a list of the currently hot tags
#
def get_hot_tags(count=nil)
do_api :get, 'explore/get_hot_tags', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
end
# Get a list of all tags
#
def get_all_tags(count=nil)
do_api :get, 'explore/get_all_tags', { :api_key => @api_key }.merge(count ? { :count => count} : {}), false
end
# Search room name, description, and tags for keywords. Keywords can be a String or an Array.
#
def search(keywords)
do_api :get, 'explore/search', { :api_key => @api_key, :q => keywords.is_a?(Array) ? keywords.join(',') : keywords }, false
end
# Search room tags. Tagnames can be a String or an Array.
#
def search_tags(tagnames)
do_api :get, 'explore/search_tags', { :api_key => @api_key, :q => tagnames.is_a?(Array) ? tagnames.join(',') : tagnames }, false
end
# Search archives. If room_id is non-nil, the search is limited to the archives of that room.
#
def search_archives(query, room_id=nil)
params = { :api_key => @api_key, :q => query }
params.merge!({ :id => room_id }) if room_id
do_api :get, 'explore/search_archives', params, false
end
# Authenticate a user within the current API session
#
def login(email, password)
do_api :post, 'auth/login', { :session => @session, :email => email, :password => password }
end
# Log out the currently-authenticated user in the session, if any
#
def logout
do_api :post, 'auth/logout', { :session => @session }
end
# Get information about the currently-authenticated user
#
def get_user_info
do_api :get, 'user/get_info', { :session => @session }
end
# Start observing the currently-authenticated user
#
def start_observing_user
do_api :post, 'user/start_observing', { :session => @session }
end
# Observe the currently-authenticated user, watching for profile changes
#
def observe_user(ticket, counter)
do_api :get, 'user/observe', { :session => @session, :ticket => ticket, :counter => counter }
end
# Stop observing the currently-authenticated user
#
def stop_observing_user(ticket)
do_api :post, 'user/stop_observing', { :session => @session, :ticket =>ticket }
end
# Get information about a chatroom, including room description, current occupants, recent messages, etc.
#
def get_room_info(room_id, counter=nil, password=nil)
params = { :api_key => @api_key, :id => room_id }
params.merge!({ :counter => counter }) if counter
params.merge!({ :password => password }) if password
do_api :get, 'room/get_info', params, false
end
# Create a chatroom
#
# options is a Hash containing any of the parameters allowed for room.create. If the :image key is present
# in options, its value must be a hash with the keys :filename, :mime_type, and :io
#
def create_room(options)
do_api :post, 'room/create', options.merge({ :session => @session })
end
# Change the settings for a chatroom
#
# options is a Hash containing any of the parameters allowed for room.create. If the :image key is present
# in options, its value must be a hash with the keys :filename, :mime_type, and :io. To change the id for
# a room, use the key :new_id
#
def change_settings(room_id, options)
do_api :post, 'room/change_settings', options.merge({ :session => @session })
end
# Delete a chatroom
#
def delete_room(room_id)
do_api :post, 'room/delete', { :id => room_id, :session => @session }
end
# Enter a chatroom
#
def enter_room(room_id, nickname=nil, password=nil, idempotent=false)
params = { :session => @session, :id => room_id }
params.merge!({ :nickname => nickname }) if nickname
params.merge!({ :password => password }) if password
params.merge!({ :idempotent => 'true' }) if idempotent
do_api :post, 'room/enter', params
end
# Poll for messages in a chatroom
#
def get_messages(ticket, counter, user_messages_only=false)
do_api :get, 'room/get_messages', { :session => @session, :ticket => ticket, :counter => counter, :user_messages_only => user_messages_only }
end
# Observe a chatroom, waiting for events to occur in the room
#
def observe_room(ticket, counter)
do_api :get, 'room/observe', { :session => @session, :ticket => ticket, :counter => counter }
end
# Set your nickname in a chatroom
#
def set_nickname(ticket, nickname)
do_api :post, 'room/set_nickname', { :session => @session, :ticket => ticket, :nickname => nickname }
end
# Say something in a chatroom. If target_occupant_id is not nil, a private message
# is sent to the indicated occupant.
#
def say(ticket, msg, target_occupant_id = nil)
params = { :session => @session, :ticket => ticket, :message => msg }
params.merge!({ :occupant_id => target_occupant_id}) if target_occupant_id
do_api :post, 'room/say', params
end
# Exit a chatroom
#
def exit_room(ticket)
do_api :post, 'room/exit', { :session => @session, :ticket => ticket }
end
private
def do_api(method, path, parameters, require_session=true)
if require_session and !@session
raise ClientError, "not in a session"
end
response = Timeout.timeout(@timeout) {
JSON.parse(self.send(method, url_for(path), parameters.merge({ :format => 'json' })))
}
unless success?(response)
raise APIError, response["error"]
end
response
end
def url_for(method)
"http://#{@host}/#{@@PATH_BASE}#{method}"
end
def get(url, params)
uri = URI.parse(url)
path = uri.path
q = params.inject("?") {|s, p| s << "#{p[0].to_s}=#{URI.encode(p[1].to_s, /./)}&"}.chop
path << q if q.length > 0
Net::HTTP.start(uri.host, uri.port) do |http|
http.read_timeout = @timeout
req = Net::HTTP::Get.new(path)
req.basic_auth(uri.user, uri.password) if uri.user
parse_result http.request(req)
end
end
def post(url, params)
if !params.find {|p| p[1].is_a?(Hash)}
params = params.inject({}){|hash,(k,v)| hash[k.to_s] = v; hash}
parse_result Net::HTTP.post_form(URI.parse(url), params)
else
boundary = 'lingr-api-client' + (0x1000000 + rand(0x1000000).to_s(16))
query = params.collect { |p|
ret = ["--#{boundary}"]
if p[1].is_a?(Hash)
ret << "Content-Disposition: form-data; name=\"#{URI.encode(p[0].to_s)}\"; filename=\"#{p[1][:filename]}\""
ret << "Content-Transfer-Encoding: binary"
ret << "Content-Type: #{p[1][:mime_type]}"
ret << ""
ret << p[1][:io].read
else
ret << "Content-Disposition: form-data; name=\"#{URI.encode(p[0].to_s)}\""
ret << ""
ret << p[1]
end
ret.join("\r\n")
}.join('') + "--#{boundary}--\r\n"
uri = URI.parse(url)
Net::HTTP.start(uri.host, uri.port) do |http|
http.read_timeout = @timeout
parse_result http.post2(uri.path, query, "Content-Type" => "multipart/form-data; boundary=#{boundary}")
end
end
end
def parse_result(result)
return nil if !result || result.code != '200' || (!result['Content-Type'] || result['Content-Type'].index('text/javascript') != 0)
# puts
# puts
# puts result.body
# puts
# puts
result.body
end
def success?(response)
return false if !response
response["status"] and response["status"] == 'ok'
end
@@PATH_BASE = 'api/'
end
end
net-irc-0.0.9/examples/client.rb 0000755 0001750 0001750 00000000525 11604616526 016352 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:fileencoding=UTF-8:
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "rubygems"
require "net/irc"
require "pp"
class SimpleClient < Net::IRC::Client
def initialize(*args)
super
end
end
SimpleClient.new("foobar", "6667", {
:nick => "foobartest",
:user => "foobartest",
:real => "foobartest",
}).start
net-irc-0.0.9/examples/mixi.rb 0000755 0001750 0001750 00000012103 11604616526 016035 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
=begin
## Licence
Ruby's by cho45
=end
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding # json use this
require "rubygems"
require "json"
require "net/irc"
require "mechanize"
# Mixi from mixi.vim by ujihisa!
class Mixi
def initialize(email, password, mixi_premium = false, image_dir = '~/.vim/mixi_images')
require 'kconv'
require 'rubygems'
require 'mechanize'
@image_dir = File.expand_path image_dir
@email, @password, @mixi_premium =
email, password, mixi_premium
end
def post(title, body, images)
@agent = WWW::Mechanize.new
@agent.user_agent_alias = 'Mac Safari'
page = @agent.get 'http://mixi.jp/home.pl'
form = page.forms[0]
form.email = @email
form.password = @password
@agent.submit form
page = @agent.get "http://mixi.jp/home.pl"
#page = @agent.get page.links[18].uri
page = @agent.get page.links[14].uri
form = page.forms[1]
#form = page.forms[(@mixi_premium ? 1 : 0)]
form.diary_title = title
form.diary_body = self.class.magic_body(body)
get_image images
images[0, 3].each_with_index do |img, i|
if /darwin/ =~ RUBY_PLATFORM && /\.png$/i =~ img
imgjpg = '/tmp/mixi-vim-' << File.basename(img).sub(/\.png$/i, '.jpg')
system "sips -s format jpeg --out #{imgjpg} #{img} > /dev/null 2>&1"
img = imgjpg
end
form.file_uploads[i].file_name = img
end
page = @agent.submit form
page = @agent.submit page.forms[1]
end
def get_latest
page = @agent.get 'http://mixi.jp/list_diary.pl'
["http://mixi.jp/" << page.links[33].uri.to_s.toutf8,
page.links[33].text.toutf8]
end
def self.magic_body(body)
body.gsub(/^( )+/) {|i| ' '.toeuc * (i.length/2) }
end
def get_image(images)
images.each_with_index do |img, i|
if img =~ %r{^http://}
path =
File.join @image_dir, i.to_s + File.extname(img)
unless File.exist? @image_dir
Dir.mkdir @image_dir
else
Dir.chdir(@image_dir) do
Dir.entries(@image_dir).
each {|f| File.unlink f if File.file? f }
end
end
system "wget -O #{path} #{img} > /dev/null 2>&1"
if File.exist? path and !File.zero? path
images[i] = path
else
images.delete_at i
end
end
end
end
end
class MixiDiary < Net::IRC::Server::Session
def server_name
"mixi"
end
def server_version
"0.0.0"
end
def main_channel
"#mixi"
end
def initialize(*args)
super
@ua = WWW::Mechanize.new
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts ||= []
@mixi = Mixi.new(@real, @pass)
@cont = []
end
def on_disconnected
@observer.kill rescue nil
end
def on_privmsg(m)
super
# CTCP にしたほうがよくないか?
case m[1]
when "."
title, *body = *@cont
@mixi.post ">_<× < #{title}".toeuc, body.join("\n").toeuc, []
@mixi.get_latest.each do |line|
post server_name, NOTICE, main_channel, line.chomp
end
when "c"
@cont.clear
post server_name, NOTICE, main_channel, "cleared."
when "p"
@cont.each do |l|
post server_name, NOTICE, main_channel, l
end
post server_name, NOTICE, main_channel, "^^end"
when "d"
post server_name, NOTICE, main_channel, "Deleted last line: #{@cont.pop}"
else
@cont << m[1]
if @cont.size == 1
post server_name, NOTICE, main_channel, "start with title: #{@cont.first}"
else
end
end
end
def on_ctcp(target, message)
end
def on_whois(m)
end
def on_who(m)
end
def on_join(m)
end
def on_part(m)
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16701,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
trap("SIGINT") { exit! 0 }
trap("SIGTERM") { exit! 0 }
trap("SIGHUP") { exit! 0 }
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f
STDERR.reopen f
}
yield
end
exit! 0
end
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], MixiDiary, opts).start
end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/examples/tig.rb 0000755 0001750 0001750 00000164145 11604616526 015670 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$KCODE = "u" unless defined? ::Encoding # json use this
=begin
# tig.rb
Ruby version of TwitterIrcGateway
## Launch
$ ruby tig.rb
If you want to help:
$ ruby tig.rb --help
## Configuration
Options specified by after IRC realname.
Configuration example for Tiarra .
general {
server-in-encoding: utf8
server-out-encoding: utf8
client-in-encoding: utf8
client-out-encoding: utf8
}
networks {
name: tig
}
tig {
server: localhost 16668
password: password on Twitter
# Recommended
name: username mentions tid
# Same as TwitterIrcGateway.exe.config.sample
# (90, 360 and 300 seconds)
#name: username dm ratio=4:1 maxlimit=50
#name: username dm ratio=20:5:6 maxlimit=62 mentions
#
#
# (60, 360 and 150 seconds)
#name: username dm ratio=30:5:12 maxlimit=94 mentions
#
#
# (36, 360 and 150 seconds)
#name: username dm ratio=50:5:12 maxlimit=134 mentions
#
# for Jabber
#name: username jabber=username@example.com:jabberpasswd
}
### athack
If `athack` client option specified,
all nick in join message is leading with @.
So if you complemente nicks (e.g. Irssi),
it's good for Twitter like reply command (@nick).
In this case, you will see torrent of join messages after connected,
because NAMES list can't send @ leading nick (it interpreted op.)
### tid[=[,]]
Apply ID to each message for make favorites by CTCP ACTION.
/me fav [ID...]
and can be
0 => white
1 => black
2 => blue navy
3 => green
4 => red
5 => brown maroon
6 => purple
7 => orange olive
8 => yellow
9 => lightgreen lime
10 => teal
11 => lightcyan cyan aqua
12 => lightblue royal
13 => pink lightpurple fuchsia
14 => grey
15 => lightgrey silver
### jabber=:
If `jabber=:` option specified,
use Jabber to get friends timeline.
You must setup im notifing settings in the site and
install "xmpp4r-simple" gem.
$ sudo gem install xmpp4r-simple
Be careful for managing password.
### alwaysim
Use IM instead of any APIs (e.g. post)
### ratio=:[:]
"121:6:20" by default.
/me ratios
Ratio | Timeline | DM | Mentions |
---------+----------+-------+----------|
1 | 24s | N/A | N/A |
141:6 | 26s | 10m OR N/A |
135:12 | 27s | 5m OR N/A |
135:6:6 | 27s | 10m | 10m |
---------+----------+-------+----------|
121:6:20 | 30s | 10m | 3m |
---------+----------+-------+----------|
4:1 | 31s | 2m1s | N/A |
50:5:12 | 49s | 8m12s | 3m25s |
20:5:6 | 57s | 3m48s | 3m10s |
30:5:12 | 58s | 5m45s | 2m24s |
1:1:1 | 1m13s | 1m13s | 1m13s |
---------------------------------------+
(Hourly limit: 150)
### dm[=]
### mentions[=]
### maxlimit=
### clientspoofing
### httpproxy=[[:]@][:]
### main_channel=
### api_source=
### check_friends_interval=
### check_updates_interval=
Set 0 to disable checking.
### old_style_reply
### tmap_size=
### strftime=
### untiny_whole_urls
### bitlify=::
### unuify
### shuffled_tmap
### ll=,
### with_retweets
## Extended commands through the CTCP ACTION
### list (ls)
/me list NICK [NUMBER]
### fav (favorite, favourite, unfav, unfavorite, unfavourite)
/me fav [ID...]
/me unfav [ID...]
/me fav! [ID...]
/me fav NICK
### link (ln, url, u)
/me link ID [ID...]
### destroy (del, delete, miss, oops, remove, rm)
/me destroy [ID...]
### in (location)
/me in Sugamo, Tokyo, Japan
### reply (re, mention)
/me reply ID blah, blah...
### retweet (rt)
/me retweet ID (blah, blah...)
### utf7 (utf-7)
/me utf7
### name
/me name My Name
### description (desc)
/me description blah, blah...
### spoof
/me spoof
/me spoo[o...]f
/me spoof tigrb twitterircgateway twitt web mobileweb
### bot (drone)
/me bot NICK [NICK...]
## Feed
## License
Ruby's by cho45
=end
case
when File.directory?("lib")
$LOAD_PATH << "lib"
when File.directory?(File.expand_path("lib", ".."))
$LOAD_PATH << File.expand_path("lib", "..")
end
require "rubygems"
require "net/irc"
require "net/https"
require "uri"
require "time"
require "logger"
require "yaml"
require "pathname"
require "ostruct"
require "json"
begin
require "iconv"
require "punycode"
rescue LoadError
end
module Net::IRC::Constants; RPL_WHOISBOT = "335"; RPL_CREATEONTIME = "329"; end
class TwitterIrcGateway < Net::IRC::Server::Session
@@ctcp_action_commands = []
class << self
def ctcp_action(*commands, &block)
name = "+ctcp_action_#{commands.inspect}"
define_method(name, block)
commands.each do |command|
@@ctcp_action_commands << [command, name]
end
end
end
def server_name
"twittergw"
end
def server_version
head = `git rev-parse HEAD 2>/dev/null`
head.empty?? "unknown" : head
end
def available_user_modes
"o"
end
def available_channel_modes
"mnti"
end
def main_channel
@opts.main_channel || "#twitter"
end
def api_base(secure = true)
URI("http#{"s" if secure}://twitter.com/")
end
def api_source
"#{@opts.api_source || "tigrb"}"
end
def jabber_bot_id
"twitter@twitter.com"
end
def hourly_limit
150
end
class APIFailed < StandardError; end
MAX_MODE_PARAMS = 3
WSP_REGEX = Regexp.new("\\r\\n|[\\r\\n\\t#{"\\u00A0\\u1680\\u180E\\u2002-\\u200D\\u202F\\u205F\\u2060\\uFEFF" if "\u0000" == "\000"}]")
def initialize(*args)
super
@groups = {}
@channels = [] # joined channels (groups)
@nicknames = {}
@drones = []
@config = Pathname.new(ENV["HOME"]) + ".tig" ### TODO マルチユーザに対応してない
@etags = {}
@consums = []
@limit = hourly_limit
@friends =
@sources =
@rsuffix_regex =
@im =
@im_thread =
@utf7 =
@httpproxy = nil
load_config
end
def on_user(m)
super
@real, *@opts = (@opts.name || @real).split(" ")
@opts = @opts.inject({}) do |r, i|
key, value = i.split("=", 2)
key = "mentions" if key == "replies" # backcompat
r.update key => case value
when nil then true
when /\A\d+\z/ then value.to_i
when /\A(?:\d+\.\d*|\.\d+)\z/ then value.to_f
else value
end
end
@opts = OpenStruct.new(@opts)
@opts.httpproxy.sub!(/\A(?:([^:@]+)(?::([^@]+))?@)?([^:]+)(?::(\d+))?\z/) do
@httpproxy = OpenStruct.new({
:user => $1, :password => $2, :address => $3, :port => $4.to_i,
})
$&.sub(/[^:@]+(?=@)/, "********")
end if @opts.httpproxy
retry_count = 0
begin
@me = api("account/update_profile") #api("account/verify_credentials")
rescue APIFailed => e
@log.error e.inspect
sleep 1
retry_count += 1
retry if retry_count < 3
log "Failed to access API 3 times." <<
" Please check your username/email and password combination, " <<
" Twitter Status and try again later."
finish
end
@prefix = prefix(@me)
@user = @prefix.user
@host = @prefix.host
#post NICK, @me.screen_name if @nick != @me.screen_name
post server_name, MODE, @nick, "+o"
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+mto", @nick
post server_name, MODE, main_channel, "+q", @nick
if @me.status
@me.status.user = @me
post @prefix, TOPIC, main_channel, generate_status_message(@me.status.text)
end
if @opts.jabber
jid, pass = @opts.jabber.split(":", 2)
@opts.jabber.replace("jabber=#{jid}:********")
if jabber_bot_id
begin
require "xmpp4r-simple"
start_jabber(jid, pass)
rescue LoadError
log "Failed to start Jabber."
log 'Installl "xmpp4r-simple" gem or check your ID/pass.'
finish
end
else
@opts.delete_field :jabber
log "This gateway does not support Jabber bot."
end
end
log "Client options: #{@opts.marshal_dump.inspect}"
@log.info "Client options: #{@opts.inspect}"
@opts.tid = begin
c = @opts.tid # expect: 0..15, true, "0,1"
b = nil
c, b = c.split(",", 2).map {|i| i.to_i } if c.respond_to? :split
c = 10 unless (0 .. 15).include? c # 10: teal
if (0 .. 15).include?(b)
"\003%.2d,%.2d[%%s]\017" % [c, b]
else
"\003%.2d[%%s]\017" % c
end
end if @opts.tid
@ratio = (@opts.ratio || "121").split(":")
@ratio = Struct.new(:timeline, :dm, :mentions).new(*@ratio)
@ratio.dm ||= @opts.dm == true ? @opts.mentions ? 6 : 26 : @opts.dm
@ratio.mentions ||= @opts.mentions == true ? @opts.dm ? 20 : 26 : @opts.mentions
@check_friends_thread = Thread.start do
loop do
begin
check_friends
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep @opts.check_friends_interval || 3600
end
end
return if @opts.jabber
@timeline = TypableMap.new(@opts.tmap_size || 10_404,
@opts.shuffled_tmap || false)
if @opts.clientspoofing
update_sources
else
@sources = [api_source]
end
update_redundant_suffix
@check_updates_thread = Thread.start do
sleep 30
loop do
begin
@log.info "check_updates"
check_updates
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 0.01 * (90 + rand(21)) *
(@opts.check_updates_interval || 86400) # 0.9 ... 1.1 day
end
sleep @opts.check_updates_interval || 86400
end
@check_timeline_thread = Thread.start do
sleep 2 * (@me.friends_count / 100.0).ceil
loop do
begin
check_timeline
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep interval(@ratio.timeline)
end
end
@check_dms_thread = Thread.start do
loop do
begin
check_direct_messages
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep interval(@ratio.dm)
end
end if @opts.dm
@check_mentions_thread = Thread.start do
sleep interval(@ratio.timeline) / 2
loop do
begin
check_mentions
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep interval(@ratio.mentions)
end
end if @opts.mentions
end
def on_disconnected
@check_friends_thread.kill rescue nil
@check_timeline_thread.kill rescue nil
@check_mentions_thread.kill rescue nil
@check_dms_thread.kill rescue nil
@check_updates_thread.kill rescue nil
@im_thread.kill rescue nil
@im.disconnect rescue nil
end
def on_privmsg(m)
target, mesg = *m.params
m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
return if mesg.empty?
return on_ctcp_action(target, mesg) if mesg.sub!(/\A +/, "") #and @opts.direct_action
command, params = mesg.split(" ", 2)
case command.downcase # TODO: escape recursive
when "d", "dm"
screen_name, mesg = params.split(" ", 2)
unless screen_name or mesg
log 'Send "d NICK message" to send a direct (private) message.' <<
" You may reply to a direct message the same way."
return
end
m.params[0] = screen_name.sub(/\A@/, "")
m.params[1] = mesg #.rstrip
return on_privmsg(m)
# TODO
#when "f", "follow"
#when "on"
#when "off" # BUG if no args
#when "g", "get"
#when "w", "whois"
#when "n", "nudge" # BUG if no args
#when "*", "fav"
#when "delete"
#when "stats" # no args
#when "leave"
#when "invite"
end unless command.nil?
mesg = escape_http_urls(mesg)
mesg = @opts.unuify ? unuify(mesg) : bitlify(mesg)
mesg = Iconv.iconv("UTF-7", "UTF-8", mesg).join.encoding!("ASCII-8BIT") if @utf7
ret = nil
retry_count = 3
begin
case
when target.ch?
if @opts.alwaysim and @im and @im.connected? # in Jabber mode, using Jabber post
ret = @im.deliver(jabber_bot_id, mesg)
post @prefix, TOPIC, main_channel, mesg
else
previous = @me.status
if previous and
((Time.now - Time.parse(previous.created_at)).to_i < 60 rescue true) and
mesg.strip == previous.text
log "You can't submit the same status twice in a row."
return
end
q = { :status => mesg, :source => source }
if @opts.old_style_reply and mesg[/\A@(?>([A-Za-z0-9_]{1,15}))[^A-Za-z0-9_]/]
if user = friend($1) || api("users/show/#{$1}")
unless user.status
user = api("users/show/#{user.id}", {},
{ :authenticate => user.protected })
end
if user.status
q.update :in_reply_to_status_id => user.status.id
end
end
end
if @opts.ll
lat, long = @opts.ll.split(",", 2)
q.update :lat => lat.to_f
q.update :long => long.to_f
end
ret = api("statuses/update", q)
log oops(ret) if ret.truncated
ret.user.status = ret
@me = ret.user
log "Status updated"
end
when target.screen_name? # Direct message
ret = api("direct_messages/new", { :screen_name => target, :text => mesg })
post server_name, NOTICE, @nick, "Your direct message has been sent to #{target}."
else
post server_name, ERR_NOSUCHNICK, target, "No such nick/channel"
end
rescue => e
@log.error [retry_count, e.inspect].inspect
if retry_count > 0
retry_count -= 1
@log.debug "Retry to setting status..."
retry
end
log "Some Error Happened on Sending #{mesg}. #{e}"
end
end
def on_whois(m)
nick = m.params[0]
unless nick.screen_name?
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
return
end
unless user = user(nick)
if api("users/username_available", { :username => nick }).valid
# TODO: 404 suspended
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
return
end
user = api("users/show/#{nick}", {}, { :authenticate => false })
end
prefix = prefix(user)
desc = user.name
desc = "#{desc} / #{user.description}".gsub(/\s+/, " ") if user.description and not user.description.empty?
signon_at = Time.parse(user.created_at).to_i rescue 0
idle_sec = (Time.now - (user.status ? Time.parse(user.status.created_at) : signon_at)).to_i rescue 0
location = user.location
location = "SoMa neighborhood of San Francisco, CA" if location.nil? or location.empty?
post server_name, RPL_WHOISUSER, @nick, nick, prefix.user, prefix.host, "*", desc
post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, location
post server_name, RPL_WHOISIDLE, @nick, nick, "#{idle_sec}", "#{signon_at}", "seconds idle, signon time"
post server_name, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
if @drones.include?(user.id)
post server_name, RPL_WHOISBOT, @nick, nick, "is a \002Bot\002 on #{server_name}"
end
end
def on_who(m)
channel = m.params[0]
whoreply = Proc.new do |ch, user|
# "
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
# :"
prefix = prefix(user)
server = api_base.host
mode = case prefix.nick
when @nick then "~"
#when @drones.include?(user.id) then "%" # FIXME
else "+"
end
hop = prefix.host.count("/")
real = user.name
post server_name, RPL_WHOREPLY, @nick,
ch, prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "#{hop} #{real}"
end
case
when channel.casecmp(main_channel).zero?
users = [@me]
users.concat @friends.reverse if @friends
users.each {|friend| whoreply.call channel, friend }
post server_name, RPL_ENDOFWHO, @nick, channel
when (@groups.key?(channel) and @friends)
@groups[channel].each do |nick|
whoreply.call channel, friend(nick)
end
post server_name, RPL_ENDOFWHO, @nick, channel
else
post server_name, ERR_NOSUCHNICK, @nick, "No such nick/channel"
end
end
def on_join(m)
channels = m.params[0].split(/ *, */)
channels.each do |channel|
channel = channel.split(" ", 2).first
next if channel.casecmp(main_channel).zero?
@channels << channel
@channels.uniq!
post @prefix, JOIN, channel
post server_name, MODE, channel, "+mtio", @nick
post server_name, MODE, channel, "+q", @nick
save_config
end
end
def on_part(m)
channel = m.params[0]
return if channel.casecmp(main_channel).zero?
@channels.delete(channel)
post @prefix, PART, channel, "Ignore group #{channel}, but setting is alive yet."
end
def on_invite(m)
nick, channel = *m.params
if not nick.screen_name? or @nick.casecmp(nick).zero?
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel" # or yourself
return
end
friend = friend(nick)
case
when channel.casecmp(main_channel).zero?
case
when friend #TODO
when api("users/username_available", { :username => nick }).valid
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
else
user = api("friendships/create/#{nick}")
join main_channel, [user]
@friends << user if @friends
@me.friends_count += 1
end
when friend
((@groups[channel] ||= []) << friend.screen_name).uniq!
join channel, [friend]
save_config
else
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
def on_kick(m)
channel, nick, msg = *m.params
if channel.casecmp(main_channel).zero?
@friends.delete_if do |friend|
if friend.screen_name.casecmp(nick).zero?
user = api("friendships/destroy/#{friend.id}")
if user.is_a? User
post prefix(user), PART, main_channel, "Removed: #{msg}"
@me.friends_count -= 1
end
end
end if @friends
else
friend = friend(nick)
if friend
(@groups[channel] ||= []).delete(friend.screen_name)
post prefix(friend), PART, channel, "Removed: #{msg}"
save_config
else
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
end
#def on_nick(m)
# @nicknames[@nick] = m.params[0]
#end
def on_topic(m)
channel = m.params[0]
return if not channel.casecmp(main_channel).zero? or @me.status.nil?
return if not @opts.mesautofix
begin
require "levenshtein"
topic = m.params[1]
previous = @me.status
return unless previous
distance = Levenshtein.normalized_distance(previous.text, topic)
return if distance.zero?
status = api("statuses/update", { :status => topic, :source => source })
log oops(ret) if status.truncated
status.user.status = status
@me = status.user
if distance < 0.5
deleted = api("statuses/destroy/#{previous.id}")
@timeline.delete_if {|tid, s| s.id == deleted.id }
log "Similar update in previous. Conclude that it has error."
log "And overwrite previous as new status: #{status.text}"
else
log "Status updated"
end
rescue LoadError
end
end
def on_mode(m)
channel = m.params[0]
unless m.params[1]
case
when channel.ch?
mode = "+mt"
mode += "i" unless channel.casecmp(main_channel).zero?
post server_name, RPL_CHANNELMODEIS, @nick, channel, mode
#post server_name, RPL_CREATEONTIME, @nick, channel, 0
when channel.casecmp(@nick).zero?
post server_name, RPL_UMODEIS, @nick, @nick, "+o"
end
end
end
private
def on_ctcp(target, mesg)
type, mesg = mesg.split(" ", 2)
method = "on_ctcp_#{type.downcase}".to_sym
send(method, target, mesg) if respond_to? method, true
end
def on_ctcp_action(target, mesg)
#return unless main_channel.casecmp(target).zero?
command, *args = mesg.split(" ")
if command
command.downcase!
@@ctcp_action_commands.each do |define, name|
if define === command
send(name, target, mesg, Regexp.last_match || command, args)
break
end
end
else
commands = @@ctcp_action_commands.map {|define, name|
define
}.select {|define|
define.is_a? String
}
log "[tig.rb] CTCP ACTION COMMANDS:"
commands.each_slice(5) do |c|
log c.join(" ")
end
end
rescue APIFailed => e
log e.inspect
rescue Exception => e
log e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
ctcp_action "call" do |target, mesg, command, args|
if args.size < 2
log "/me call as "
return
end
screen_name = args[0]
nickname = args[2] || args[1] # allow omitting "as"
if nickname == "is" and
deleted_nick = @nicknames.delete(screen_name)
log %Q{Removed the nickname "#{deleted_nick}" for #{screen_name}}
else
@nicknames[screen_name] = nickname
log "Call #{screen_name} as #{nickname}"
end
#save_config
end
ctcp_action "debug" do |target, mesg, command, args|
code = args.join(" ")
begin
log instance_eval(code).inspect
rescue Exception => e
log e.inspect
end
end
ctcp_action "utf-7", "utf7" do |target, mesg, command, args|
unless defined? ::Iconv
log "Can't load iconv."
return
end
@utf7 = !@utf7
log "UTF-7 mode: #{@utf7 ? 'on' : 'off'}"
end
ctcp_action "list", "ls" do |target, mesg, command, args|
if args.empty?
log "/me list []"
return
end
nick = args.first
if not nick.screen_name? or
api("users/username_available", { :username => nick }).valid
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
return
end
id = nick
authenticate = false
if user = friend(nick)
id = user.id
nick = user.screen_name
authenticate = user.protected
end
unless (1..200).include?(count = args[1].to_i)
count = 20
end
begin
res = api("statuses/user_timeline/#{id}",
{ :count => count }, { :authenticate => authenticate })
rescue APIFailed
#log "#{nick} has protected their updates."
return
end
res.reverse_each do |s|
message(s, target, nil, nil, NOTICE)
end
end
ctcp_action %r/\A(un)?fav(?:ou?rite)?(!)?\z/ do |target, mesg, command, args|
# fav, unfav, favorite, unfavorite, favourite, unfavourite
method = command[1].nil? ? "create" : "destroy"
force = !!command[2]
entered = command[0].capitalize
statuses = []
if args.empty?
if method == "create"
if status = @timeline.last
statuses << status
else
#log ""
return
end
else
@favorites ||= api("favorites").reverse
if @favorites.empty?
log "You've never favorite yet. No favorites to unfavorite."
return
end
statuses.push @favorites.last
end
else
args.each do |tid_or_nick|
case
when status = @timeline[tid = tid_or_nick]
statuses.push status
when friend = friend(nick = tid_or_nick)
if friend.status
statuses.push friend.status
else
log "#{tid_or_nick} has no status."
end
else
# PRIVMSG: fav nick
log "No such ID/NICK #{@opts.tid % tid_or_nick}"
end
end
end
@favorites ||= []
statuses.each do |s|
if not force and method == "create" and
@favorites.find {|i| i.id == s.id }
log "The status is already favorited! <#{permalink(s)}>"
next
end
res = api("favorites/#{method}/#{s.id}")
log "#{entered}: #{res.user.screen_name}: #{generate_status_message(res.text)}"
if method == "create"
@favorites.push res
else
@favorites.delete_if {|i| i.id == res.id }
end
end
end
ctcp_action "link", "ln", /\Au(?:rl)?\z/ do |target, mesg, command, args|
args.each do |tid|
if status = @timeline[tid]
log "#{@opts.tid % tid}: #{permalink(status)}"
else
log "No such ID #{@opts.tid % tid}"
end
end
end
ctcp_action "ratio", "ratios" do |target, mesg, command, args|
unless args.empty?
args = args.first.split(":") if args.size == 1
case
when @opts.dm && @opts.mentions && args.size < 3
log "/me ratios "
return
when @opts.dm && args.size < 2
log "/me ratios "
return
when @opts.mentions && args.size < 2
log "/me ratios "
return
end
ratios = args.map {|ratio| ratio.to_f }
if ratios.any? {|ratio| ratio <= 0.0 }
log "Ratios must be greater than 0.0 and fractional values are permitted."
return
end
@ratio.timeline = ratios[0]
case
when @opts.dm
@ratio.dm = ratios[1]
@ratio.mentions = ratios[2] if @opts.mentions
when @opts.mentions
@ratio.mentions = ratios[1]
end
end
log "Intervals: " + @ratio.zip([:timeline, :dm, :mentions]).map {|ratio, name| [name, "#{interval(ratio).round}sec"] }.inspect
end
ctcp_action "rm", %r/\A(?:de(?:stroy|l(?:ete)?)|miss|oops|r(?:emove|m))\z/ do |target, mesg, command, args|
# destroy, delete, del, remove, rm, miss, oops
statuses = []
if args.empty? and @me.status
statuses.push @me.status
else
args.each do |tid|
if status = @timeline[tid]
if status.user.id == @me.id
statuses.push status
else
log "The status you specified by the ID #{@opts.tid % tid} is not yours."
end
else
log "No such ID #{@opts.tid % tid}"
end
end
end
b = false
statuses.each do |st|
res = api("statuses/destroy/#{st.id}")
@timeline.delete_if {|tid, s| s.id == res.id }
b = @me.status && @me.status.id == res.id
log "Destroyed: #{res.text}"
end
Thread.start do
sleep 2
@me = api("account/update_profile") #api("account/verify_credentials")
if @me.status
@me.status.user = @me
msg = generate_status_message(@me.status.text)
@timeline.any? do |tid, s|
if s.id == @me.status.id
msg << " " << @opts.tid % tid
end
end
post @prefix, TOPIC, main_channel, msg
end
end if b
end
ctcp_action "name" do |target, mesg, command, args|
name = mesg.split(" ", 2)[1]
unless name.nil?
@me = api("account/update_profile", { :name => name })
@me.status.user = @me if @me.status
log "You are named #{@me.name}."
end
end
ctcp_action "email" do |target, mesg, command, args|
# FIXME
email = args.first
unless email.nil?
@me = api("account/update_profile", { :email => email })
@me.status.user = @me if @me.status
end
end
ctcp_action "url" do |target, mesg, command, args|
# FIXME
url = args.first || ""
@me = api("account/update_profile", { :url => url })
@me.status.user = @me if @me.status
end
ctcp_action "in", "location" do |target, mesg, command, args|
location = mesg.split(" ", 2)[1] || ""
@me = api("account/update_profile", { :location => location })
@me.status.user = @me if @me.status
location = (@me.location and @me.location.empty?) ? "nowhere" : "in #{@me.location}"
log "You are #{location} now."
end
ctcp_action %r/\Adesc(?:ription)?\z/ do |target, mesg, command, args|
# FIXME
description = mesg.split(" ", 2)[1] || ""
@me = api("account/update_profile", { :description => description })
@me.status.user = @me if @me.status
end
ctcp_action %r/\A(?:mention|re(?:ply)?)\z/ do |target, mesg, command, args|
# reply, re, mention
tid = args.first
if status = @timeline[tid]
text = mesg.split(" ", 3)[2]
screen_name = "@#{status.user.screen_name}"
if text.nil? or not text.include?(screen_name)
text = "#{screen_name} #{text}"
end
ret = api("statuses/update", { :status => text, :source => source,
:in_reply_to_status_id => status.id })
log oops(ret) if ret.truncated
msg = generate_status_message(status.text)
url = permalink(status)
log "Status updated (In reply to #{@opts.tid % tid}: #{msg} <#{url}>)"
ret.user.status = ret
@me = ret.user
end
end
ctcp_action %r/\Aspoo(o+)?f\z/ do |target, mesg, command, args|
if args.empty?
Thread.start do
update_sources(command[1].nil?? 0 : command[1].size)
end
return
end
names = []
@sources = args.map do |arg|
names << "=#{arg}"
case arg.upcase
when "WEB" then ""
when "API" then nil
else arg
end
end
log(names.inject([]) do |r, name|
s = r.join(", ")
if s.size < 400
r << name
else
log s
[name]
end
end.join(", "))
end
ctcp_action "bot", "drone" do |target, mesg, command, args|
if args.empty?
log "/me bot [...]"
return
end
args.each do |bot|
user = friend(bot)
unless user
post server_name, ERR_NOSUCHNICK, bot, "No such nick/channel"
next
end
if @drones.delete(user.id)
mode = "-#{mode}"
log "#{bot} is no longer a bot."
else
@drones << user.id
mode = "+#{mode}"
log "Marks #{bot} as a bot."
end
end
save_config
end
ctcp_action "home", "h" do |target, mesg, command, args|
if args.empty?
log "/me home "
return
end
nick = args.first
if not nick.screen_name? or
api("users/username_available", { :username => nick }).valid
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
return
end
log "http://twitter.com/#{nick}"
end
ctcp_action "retweet", "rt" do |target, mesg, command, args|
if args.empty?
log "/me #{command} blah blah"
return
end
tid = args.first
if status = @timeline[tid]
if args.size >= 2
comment = mesg.split(" ", 3)[2] + " "
else
comment = ""
end
screen_name = "@#{status.user.screen_name}"
rt_message = generate_status_message(status.text)
text = "#{comment}RT #{screen_name}: #{rt_message}"
ret = api("statuses/update", { :status => text, :source => source })
log oops(ret) if ret.truncated
log "Status updated (RT to #{@opts.tid % tid}: #{text})"
ret.user.status = ret
@me = ret.user
end
end
def on_ctcp_clientinfo(target, msg)
if user = user(target)
post prefix(user), NOTICE, @nick, ctcp_encode("CLIENTINFO :CLIENTINFO USERINFO VERSION TIME")
end
end
def on_ctcp_userinfo(target, msg)
user = user(target)
if user and not user.description.empty?
post prefix(user), NOTICE, @nick, ctcp_encode("USERINFO :#{user.description}")
end
end
def on_ctcp_version(target, msg)
user = user(target)
if user and user.status
source = user.status.source
version = source.gsub(/<[^>]*>/, "").strip
version << " <#{$1}>" if / href="([^"]+)/ === source
post prefix(user), NOTICE, @nick, ctcp_encode("VERSION :#{version}")
end
end
def on_ctcp_time(target, msg)
if user = user(target)
offset = user.utc_offset
post prefix(user), NOTICE, @nick, ctcp_encode("TIME :%s%s (%s)" % [
(Time.now + offset).utc.iso8601[0, 19],
"%+.2d:%.2d" % (offset/60).divmod(60),
user.time_zone,
])
end
end
def check_friends
if @friends.nil?
@friends = page("statuses/friends/#{@me.id}", @me.friends_count)
if @opts.athack
join main_channel, @friends
else
rest = @friends.map do |i|
prefix = "+" #@drones.include?(i.id) ? "%" : "+" # FIXME ~&%
"#{prefix}#{i.screen_name}"
end.reverse.inject("~#{@nick}") do |r, nick|
if r.size < 400
r << " " << nick
else
post server_name, RPL_NAMREPLY, @nick, "=", main_channel, r
nick
end
end
post server_name, RPL_NAMREPLY, @nick, "=", main_channel, rest
post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
end
else
new_ids = page("friends/ids/#{@me.id}", @me.friends_count)
friend_ids = @friends.reverse.map {|friend| friend.id }
(friend_ids - new_ids).each do |id|
@friends.delete_if do |friend|
if friend.id == id
post prefix(friend), PART, main_channel, ""
@me.friends_count -= 1
end
end
end
new_ids -= friend_ids
unless new_ids.empty?
new_friends = page("statuses/friends/#{@me.id}", new_ids.size)
join main_channel, new_friends.delete_if {|friend|
@friends.any? {|i| i.id == friend.id }
}.reverse
@friends.concat new_friends
@me.friends_count += new_friends.size
end
end
end
def check_timeline
cmd = PRIVMSG
path = "statuses/#{@opts.with_retweets ? "home" : "friends"}_timeline"
q = { :count => 200 }
@latest_id ||= nil
case
when @latest_id
q.update(:since_id => @latest_id)
when is_first_retrieve = !@me.statuses_count.zero? && !@me.friends_count.zero?
# cmd = NOTICE # デバッグするときめんどくさいので
q.update(:count => 20)
end
api(path, q).reverse_each do |status|
id = @latest_id = status.id
next if @timeline.any? {|tid, s| s.id == id }
status.user.status = status
user = status.user
tid = @timeline.push(status)
tid = nil unless @opts.tid
@log.debug [id, user.screen_name, status.text].inspect
if user.id == @me.id
mesg = generate_status_message(status.text)
mesg << " " << @opts.tid % tid if tid
post @prefix, TOPIC, main_channel, mesg
@me = user
else
if @friends
b = false
@friends.each_with_index do |friend, i|
if b = friend.id == user.id
if friend.screen_name != user.screen_name
post prefix(friend), NICK, user.screen_name
end
@friends[i] = user
break
end
end
unless b
join main_channel, [user]
@friends << user
@me.friends_count += 1
end
end
message(status, main_channel, tid, nil, cmd)
end
@groups.each do |channel, members|
next unless members.include?(user.screen_name)
message(status, channel, tid, nil, cmd)
end
end
end
def check_direct_messages
@prev_dm_id ||= nil
q = @prev_dm_id ? { :count => 200, :since_id => @prev_dm_id } \
: { :count => 1 }
api("direct_messages", q).reverse_each do |mesg|
unless @prev_dm_id &&= mesg.id
@prev_dm_id = mesg.id
next
end
id = mesg.id
user = mesg.sender
tid = nil
text = mesg.text
@log.debug [id, user.screen_name, text].inspect
message(user, @nick, tid, text)
end
end
def check_mentions
return if @timeline.empty?
@prev_mention_id ||= @timeline.last.id
api("statuses/mentions", {
:count => 200,
:since_id => @prev_mention_id
}).reverse_each do |mention|
id = @prev_mention_id = mention.id
next if @timeline.any? {|tid, s| s.id == id }
mention.user.status = mention
user = mention.user
tid = @timeline.push(mention)
tid = nil unless @opts.tid
@log.debug [id, user.screen_name, mention.text].inspect
message(mention, main_channel, tid)
@friends.each_with_index do |friend, i|
if friend.id == user.id
@friends[i] = user
break
end
end if @friends
end
end
def check_updates
update_redundant_suffix
uri = URI("http://github.com/api/v1/json/cho45/net-irc/commits/master")
@log.debug uri.inspect
res = http(uri).request(http_req(:get, uri))
latest = JSON.parse(res.body)['commits'][0]['id']
unless server_version == latest
log "\002New version is available.\017 run 'git pull'."
end
rescue Errno::ECONNREFUSED, Timeout::Error => e
@log.error "Failed to get the latest revision of tig.rb from #{uri.host}: #{e.inspect}"
end
def interval(ratio)
now = Time.now
max = @opts.maxlimit || 0
limit = 0.98 * @limit # 98% of the rate limit
i = 3600.0 # an hour in seconds
i *= @ratio.inject {|sum, r| sum.to_f + r.to_f } +
@consums.delete_if {|t| t < now }.size
i /= ratio.to_f
i /= (0 < max && max < limit) ? max : limit
i = 60 * 30 if i > 60 * 30 # 30分以上止まらないように。
i
rescue => e
@log.error e.inspect
100
end
def join(channel, users)
params = []
users.each do |user|
prefix = prefix(user)
post prefix, JOIN, channel
params << prefix.nick if user.protected
next if params.size < MAX_MODE_PARAMS
post server_name, MODE, channel, "+#{"v" * params.size}", *params
params = []
end
post server_name, MODE, channel, "+#{"v" * params.size}", *params unless params.empty?
users
end
def start_jabber(jid, pass)
@log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
@im = Jabber::Simple.new(jid, pass)
@im.add(jabber_bot_id)
@im_thread = Thread.start do
require "cgi"
loop do
begin
@im.received_messages.each do |msg|
@log.debug [msg.from, msg.body].inspect
if msg.from.strip == jabber_bot_id
# Twitter -> 'id: msg'
body = msg.body.sub(/\A(.+?)(?:\(([^()]+)\))?: /, "")
body = decode_utf7(body)
if Regexp.last_match
nick, id = Regexp.last_match.captures
body = untinyurl(CGI.unescapeHTML(body))
user = nick
nick = id || nick
nick = @nicknames[nick] || nick
post "#{nick}!#{user}@#{api_base.host}", PRIVMSG, main_channel, body
end
end
end
rescue Exception => e
@log.error "Error on Jabber loop: #{e.inspect}"
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 1
end
end
end
def save_config
config = {
:groups => @groups,
:channels => @channels,
#:nicknames => @nicknames,
:drones => @drones,
}
@config.open("w") {|f| YAML.dump(config, f) }
end
def load_config
@config.open do |f|
config = YAML.load(f)
@groups = config[:groups] || {}
@channels = config[:channels] || []
#@nicknames = config[:nicknames] || {}
@drones = config[:drones] || []
end
rescue Errno::ENOENT
end
def require_post?(path)
%r{
\A
(?: status(?:es)?/update \z
| direct_messages/new \z
| friendships/create/
| account/(?: end_session \z | update_ )
| favou?ri(?: ing | tes )/create/
| notifications/
| blocks/create/ )
}x === path
end
#def require_put?(path)
# %r{ \A status(?:es)?/retweet (?:/|\z) }x === path
#end
def api(path, query = {}, opts = {})
path.sub!(%r{\A/+}, "")
query = query.to_query_str
authenticate = opts.fetch(:authenticate, true)
uri = api_base(authenticate)
uri.path += path
uri.path += ".json" if path != "users/username_available"
uri.query = query unless query.empty?
header = {}
credentials = authenticate ? [@real, @pass] : nil
req = case
when path.include?("/destroy/")
http_req :delete, uri, header, credentials
when require_post?(path)
http_req :post, uri, header, credentials
#when require_put?(path)
# http_req :put, uri, header, credentials
else
http_req :get, uri, header, credentials
end
@log.debug [req.method, uri.to_s]
ret = http(uri, 30, 30).request req
#@etags[uri.to_s] = ret["ETag"]
case
when authenticate
hourly_limit = ret["X-RateLimit-Limit"].to_i
unless hourly_limit.zero?
if @limit != hourly_limit
msg = "The rate limit per hour was changed: #{@limit} to #{hourly_limit}"
log msg
@log.info msg
@limit = hourly_limit
end
#if req.is_a?(Net::HTTP::Get) and not %w{
if not %w{
statuses/friends_timeline
direct_messages
statuses/mentions
}.include?(path) and not ret.is_a?(Net::HTTPServerError)
expired_on = Time.parse(ret["Date"]) rescue Time.now
expired_on += 3636 # 1.01 hours in seconds later
@consums << expired_on
end
end
when ret["X-RateLimit-Remaining"]
@limit_remaining_for_ip = ret["X-RateLimit-Remaining"].to_i
@log.debug "IP based limit: #{@limit_remaining_for_ip}"
end
case ret
when Net::HTTPOK # 200
# Avoid Twitter's invalid JSON
json = ret.body.strip.sub(/\A(?:false|true)\z/, "[\\&]")
res = JSON.parse json
if res.is_a?(Hash) and res["error"] # and not res["response"]
if @error != res["error"]
@error = res["error"]
log @error
end
raise APIFailed, res["error"]
end
res.to_tig_struct
when Net::HTTPNoContent, # 204
Net::HTTPNotModified # 304
[]
when Net::HTTPBadRequest # 400: exceeded the rate limitation
if ret.key?("X-RateLimit-Reset")
s = ret["X-RateLimit-Reset"].to_i - Time.now.to_i
if s > 0
log "RateLimit: #{(s / 60.0).ceil} min remaining to get timeline"
sleep (s > 60 * 10) ? 60 * 10 : s # 10 分に一回はとってくるように
end
end
raise APIFailed, "#{ret.code}: #{ret.message}"
when Net::HTTPUnauthorized # 401
raise APIFailed, "#{ret.code}: #{ret.message}"
else
raise APIFailed, "Server Returned #{ret.code} #{ret.message}"
end
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
raise APIFailed, e.inspect
end
def page(path, max_count, authenticate = false)
@limit_remaining_for_ip ||= 52
limit = 0.98 * @limit_remaining_for_ip # 98% of IP based rate limit
r = []
cpp = nil # counts per page
1.upto(limit) do |num|
ret = api(path, { :page => num }, { :authenticate => authenticate })
cpp ||= ret.size
r.concat ret
break if ret.empty? or num >= max_count / cpp.to_f or
ret.size != cpp or r.size >= max_count
end
r
end
def generate_status_message(mesg)
mesg = decode_utf7(mesg)
mesg.delete!("\000\001")
mesg.gsub!(">", ">")
mesg.gsub!("<", "<")
mesg.gsub!(WSP_REGEX, " ")
mesg = untinyurl(mesg)
mesg.sub!(@rsuffix_regex, "") if @rsuffix_regex
mesg.strip
end
def friend(id)
return nil unless @friends
if id.is_a? String
@friends.find {|i| i.screen_name.casecmp(id).zero? }
else
@friends.find {|i| i.id == id }
end
end
def user(id)
if id.is_a? String
@nick.casecmp(id).zero? ? @me : friend(id)
else
@me.id == id ? @me : friend(id)
end
end
def prefix(u)
nick = u.screen_name
nick = "@#{nick}" if @opts.athack
user = "id=%.9d" % u.id
host = api_base.host
host += "/protected" if u.protected
host += "/bot" if @drones.include?(u.id)
Prefix.new("#{nick}!#{user}@#{host}")
end
def message(struct, target, tid = nil, str = nil, command = PRIVMSG)
unless str
status = struct.is_a?(Status) ? struct : struct.status
str = status.text
if command != PRIVMSG
time = Time.parse(status.created_at) rescue Time.now
str = "#{time.strftime(@opts.strftime || "%m-%d %H:%M")} #{str}" # TODO: color
end
end
user = (struct.is_a?(User) ? struct : struct.user).dup
screen_name = user.screen_name
user.screen_name = @nicknames[screen_name] || screen_name
prefix = prefix(user)
str = generate_status_message(str)
str = "#{str} #{@opts.tid % tid}" if tid
post prefix, command, target, str
end
def log(str)
post server_name, NOTICE, main_channel, str.gsub(/\r\n|[\r\n]/, " ")
end
def decode_utf7(str)
return str unless defined? ::Iconv and str.include?("+")
str.sub!(/\A(?:.+ > |.+\z)/) { Iconv.iconv("UTF-8", "UTF-7", $&).join }
#FIXME str = "[utf7]: #{str}" if str =~ /[^a-z0-9\s]/i
str
rescue Iconv::IllegalSequence
str
rescue => e
@log.error e
str
end
def untinyurl(text)
text.gsub(@opts.untiny_whole_urls ? URI.regexp(%w[http https]) : %r{
http:// (?:
(?: bit\.ly | (?: tin | rub) yurl\.com
| is\.gd | cli\.gs | tr\.im | u\.nu | airme\.us
| ff\.im | twurl.nl | bkite\.com | tumblr\.com
| pic\.gd | sn\.im | digg\.com )
/ [0-9a-z=-]+ |
blip\.fm/~ (?> [0-9a-z]+) (?! /) |
flic\.kr/[a-z0-9/]+
)
}ix) {|url| "#{resolve_http_redirect(URI(url)) || url}" }
end
def bitlify(text)
login, key, len = @opts.bitlify.split(":", 3) if @opts.bitlify
len = (len || 20).to_i
longurls = URI.extract(text, %w[http https]).uniq.map do |url|
URI.rstrip url
end.reject do |url|
url.size < len
end
return text if longurls.empty?
bitly = URI("http://api.bit.ly/shorten")
if login and key
bitly.path = "/shorten"
bitly.query = {
:version => "2.0.1", :format => "json", :longUrl => longurls,
}.to_query_str(";")
@log.debug bitly
req = http_req(:get, bitly, {}, [login, key])
res = http(bitly, 5, 10).request(req)
res = JSON.parse(res.body)
res = res["results"]
longurls.each do |longurl|
text.gsub!(longurl) do
res[$&] && res[$&]["shortUrl"] || $&
end
end
else
bitly.path = "/api"
longurls.each do |longurl|
bitly.query = { :url => longurl }.to_query_str
@log.debug bitly
req = http_req(:get, bitly)
res = http(bitly, 5, 5).request(req)
text.gsub!(longurl, res.body)
end
end
text
rescue => e
@log.error e
text
end
def unuify(text)
unu_url = "http://u.nu/"
unu = URI("#{unu_url}unu-api-simple")
size = unu_url.size
text.gsub(URI.regexp(%w[http https])) do |url|
url = URI.rstrip url
if url.size < size + 5 or url[0, size] == unu_url
return url
end
unu.query = { :url => url }.to_query_str
@log.debug unu
res = http(unu, 5, 5).request(http_req(:get, unu)).body
if res[0, 12] == unu_url
res
else
raise res.split("|")
end
end
rescue => e
@log.error e
text
end
def escape_http_urls(text)
original_text = text.encoding!("UTF-8").dup
if defined? ::Punycode
# TODO: Nameprep
text.gsub!(%r{(https?://)([^\x00-\x2C\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+)}) do
domain = $2
# Dots:
# * U+002E (full stop) * U+3002 (ideographic full stop)
# * U+FF0E (fullwidth full stop) * U+FF61 (halfwidth ideographic full stop)
# => /[.\u3002\uFF0E\uFF61] # Ruby 1.9 /x
$1 + domain.split(/\.|\343\200\202|\357\274\216|\357\275\241/).map do |label|
break [domain] if /\A-|[\x00-\x2C\x2E\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]|-\z/ === label
next label unless /[^-A-Za-z0-9]/ === label
punycode = Punycode.encode(label)
break [domain] if punycode.size > 59
"xn--#{punycode}"
end.join(".")
end
if text != original_text
log "Punycode encoded: #{text}"
original_text = text.dup
end
end
urls = []
text.split(/[\s<>]+/).each do |str|
next if /%[0-9A-Fa-f]{2}/ === str
# URI::UNSAFE + "#"
escaped_str = URI.escape(str, %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]#]})
URI.extract(escaped_str, %w[http https]).each do |url|
uri = URI(URI.rstrip(url))
if not urls.include?(uri.to_s) and exist_uri?(uri)
urls << uri.to_s
end
end if escaped_str != str
end
urls.each do |url|
unescaped_url = URI.unescape(url).encoding!("UTF-8")
text.gsub!(unescaped_url, url)
end
log "Percent encoded: #{text}" if text != original_text
text.encoding!("ASCII-8BIT")
rescue => e
@log.error e
text
end
def exist_uri?(uri, limit = 1)
ret = nil
#raise "Not supported." unless uri.is_a?(URI::HTTP)
return ret if limit.zero? or uri.nil? or not uri.is_a?(URI::HTTP)
@log.debug uri.inspect
req = http_req :head, uri
http(uri, 3, 2).request(req) do |res|
ret = case res
when Net::HTTPSuccess
true
when Net::HTTPRedirection
uri = resolve_http_redirect(uri)
exist_uri?(uri, limit - 1)
when Net::HTTPClientError
false
#when Net::HTTPServerError
# nil
else
nil
end
end
ret
rescue => e
@log.error e.inspect
ret
end
def resolve_http_redirect(uri, limit = 3)
return uri if limit.zero? or uri.nil?
@log.debug uri.inspect
req = http_req :head, uri
http(uri, 3, 2).request(req) do |res|
break if not res.is_a?(Net::HTTPRedirection) or
not res.key?("Location")
begin
location = URI(res["Location"])
rescue URI::InvalidURIError
end
unless location.is_a? URI::HTTP
begin
location = URI.join(uri.to_s, res["Location"])
rescue URI::InvalidURIError, URI::BadURIError
# FIXME
end
end
uri = resolve_http_redirect(location, limit - 1)
end
uri
rescue => e
@log.error e.inspect
uri
end
def update_sources(n = 0)
if @sources and @sources.size > 1 and n.zero?
log "tig.rb"
@sources = [api_source]
return @sources
end
uri = URI("http://wedata.net/databases/TwitterSources/items.json")
@log.debug uri.inspect
json = http(uri).request(http_req(:get, uri)).body
sources = JSON.parse json
sources.map! {|item| [item["data"]["source"], item["name"]] }
sources.push ["", "web"]
sources.push [nil, "API"]
sources = Array.new(n) do
sources.delete_at(rand(sources.size))
end if (1 ... sources.size).include?(n)
log(sources.inject([]) do |r, src|
s = r.join(", ")
if s.size < 400
r << src[1]
else
log s
[src[1]]
end
end.join(", ")) if @sources
@sources = sources.map {|src| src[0] }
rescue => e
@log.error e.inspect
log "An error occured while loading #{uri.host}."
@sources ||= [api_source]
end
def update_redundant_suffix
uri = URI("http://svn.coderepos.org/share/platform/twitterircgateway/suffixesblacklist.txt")
@log.debug uri.inspect
res = http(uri).request(http_req(:get, uri))
@etags[uri.to_s] = res["ETag"]
return if res.is_a? Net::HTTPNotModified
source = res.body
source.encoding!("UTF-8") if source.respond_to?(:encoding) and source.encoding == Encoding::BINARY
@rsuffix_regex = /#{Regexp.union(*source.split)}\z/
rescue Errno::ECONNREFUSED, Timeout::Error => e
@log.error "Failed to get the redundant suffix blacklist from #{uri.host}: #{e.inspect}"
end
def http(uri, open_timeout = nil, read_timeout = 60)
http = case
when @httpproxy
Net::HTTP.new(uri.host, uri.port, @httpproxy.address, @httpproxy.port,
@httpproxy.user, @httpproxy.password)
when ENV["HTTP_PROXY"], ENV["http_proxy"]
proxy = URI(ENV["HTTP_PROXY"] || ENV["http_proxy"])
Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port,
proxy.user, proxy.password)
else
Net::HTTP.new(uri.host, uri.port)
end
http.open_timeout = open_timeout if open_timeout # nil by default
http.read_timeout = read_timeout if read_timeout # 60 by default
if uri.is_a? URI::HTTPS
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
http
rescue => e
@log.error e
end
def http_req(method, uri, header = {}, credentials = nil)
accepts = ["*/*;q=0.1"]
#require "mime/types"; accepts.unshift MIME::Types.of(uri.path).first.simplified
types = { "json" => "application/json", "txt" => "text/plain" }
ext = uri.path[/[^.]+\z/]
accepts.unshift types[ext] if types.key?(ext)
user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)}; net-irc) Ruby/#{RUBY_VERSION} (#{RUBY_PLATFORM})"
header["User-Agent"] ||= user_agent
header["Accept"] ||= accepts.join(",")
header["Accept-Charset"] ||= "UTF-8,*;q=0.0" if ext != "json"
#header["Accept-Language"] ||= @opts.lang # "en-us,en;q=0.9,ja;q=0.5"
header["If-None-Match"] ||= @etags[uri.to_s] if @etags[uri.to_s]
req = case method.to_s.downcase.to_sym
when :get
Net::HTTP::Get.new uri.request_uri, header
when :head
Net::HTTP::Head.new uri.request_uri, header
when :post
Net::HTTP::Post.new uri.path, header
when :put
Net::HTTP::Put.new uri.path, header
when :delete
Net::HTTP::Delete.new uri.request_uri, header
else # raise ""
end
if req.request_body_permitted?
req["Content-Type"] ||= "application/x-www-form-urlencoded"
req.body = uri.query
end
req.basic_auth(*credentials) if credentials
req
rescue => e
@log.error e
end
def oops(status)
"Oops! Your update was over 140 characters. We sent the short version" <<
" to your friends (they can view the entire update on the Web <" <<
permalink(status) << ">)."
end
def permalink(struct)
path = struct.is_a?(Status) ? "#{struct.user.screen_name}/statuses/#{struct.id}" \
: struct.screen_name
"http://twitter.com/#{path}"
end
def source
@sources[rand(@sources.size)]
end
def initial_message
super
post server_name, RPL_ISUPPORT, @nick,
"PREFIX=(qov)~@%+", "CHANTYPES=#", "CHANMODES=#{available_channel_modes}",
"MODES=#{MAX_MODE_PARAMS}", "NICKLEN=15", "TOPICLEN=420", "CHANNELLEN=50",
"NETWORK=Twitter",
"are supported by this server"
end
User = Struct.new(:id, :name, :screen_name, :location, :description, :url,
:following, :notifications, :protected, :time_zone,
:utc_offset, :created_at, :friends_count, :followers_count,
:statuses_count, :favourites_count, :verified, :geo_enabled,
:profile_image_url, :profile_background_color, :profile_text_color,
:profile_link_color, :profile_sidebar_fill_color,
:profile_sidebar_border_color, :profile_background_image_url,
:profile_background_tile, :status)
Status = Struct.new(:id, :text, :source, :created_at, :truncated, :favorited, :geo,
:in_reply_to_status_id, :in_reply_to_user_id,
:in_reply_to_screen_name, :user)
DM = Struct.new(:id, :text, :created_at,
:sender_id, :sender_screen_name, :sender,
:recipient_id, :recipient_screen_name, :recipient)
Geo = Struct.new(:type, :coordinates, :geometries, :geometry, :properties, :id,
:crs, :name, :href, :bbox, :features)
class TypableMap < Hash
#Roman = %w[
# k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q
#].unshift("").map do |consonant|
# case consonant
# when "h", "q" then %w|a i e o|
# when /[hy]$/ then %w|a u o|
# else %w|a i u e o|
# end.map {|vowel| "#{consonant}#{vowel}" }
#end.flatten
Roman = %w[
a i u e o ka ki ku ke ko sa shi su se so
ta chi tsu te to na ni nu ne no ha hi fu he ho
ma mi mu me mo ya yu yo ra ri ru re ro
wa wo n
ga gi gu ge go za ji zu ze zo da de do
ba bi bu be bo pa pi pu pe po
kya kyu kyo sha shu sho cha chu cho
nya nyu nyo hya hyu hyo mya myu myo
rya ryu ryo
gya gyu gyo ja ju jo bya byu byo
pya pyu pyo
].freeze
def initialize(size = nil, shuffle = false)
if shuffle
@seq = Roman.dup
if @seq.respond_to?(:shuffle!)
@seq.shuffle!
else
@seq = Array.new(@seq.size) { @seq.delete_at(rand(@seq.size)) }
end
@seq.freeze
else
@seq = Roman
end
@n = 0
@size = size || @seq.size
end
def generate(n)
ret = []
begin
n, r = n.divmod(@seq.size)
ret << @seq[r]
end while n > 0
ret.reverse.join #.gsub(/n(?=[bmp])/, "m")
end
def push(obj)
id = generate(@n)
self[id] = obj
@n += 1
@n %= @size
id
end
alias :<< :push
def clear
@n = 0
super
end
def first
@size.times do |i|
id = generate((@n + i) % @size)
return self[id] if key? id
end unless empty?
nil
end
def last
@size.times do |i|
id = generate((@n - 1 - i) % @size)
return self[id] if key? id
end unless empty?
nil
end
private :[]=
undef update, merge, merge!, replace
end
end
class Array
def to_tig_struct
map do |v|
v.respond_to?(:to_tig_struct) ? v.to_tig_struct : v
end
end
end
class Hash
def to_tig_struct
if empty?
#warn "" if $VERBOSE
#raise Error
return nil
end
struct = case
when struct_of?(TwitterIrcGateway::User)
TwitterIrcGateway::User.new
when struct_of?(TwitterIrcGateway::Status)
TwitterIrcGateway::Status.new
when struct_of?(TwitterIrcGateway::DM)
TwitterIrcGateway::DM.new
when struct_of?(TwitterIrcGateway::Geo)
TwitterIrcGateway::Geo.new
else
members = keys
members.concat TwitterIrcGateway::User.members
members.concat TwitterIrcGateway::Status.members
members.concat TwitterIrcGateway::DM.members
members.concat TwitterIrcGateway::Geo.members
members.map! {|m| m.to_sym }
members.uniq!
Struct.new(*members).new
end
each do |k, v|
struct[k.to_sym] = v.respond_to?(:to_tig_struct) ? v.to_tig_struct : v
end
struct
end
# { :f => "v" } #=> "f=v"
# { "f" => [1, 2] } #=> "f=1&f=2"
# { "f" => "" } #=> "f="
# { "f" => nil } #=> "f"
def to_query_str separator = "&"
inject([]) do |r, (k, v)|
k = URI.encode_component k.to_s
(v.is_a?(Array) ? v : [v]).each do |i|
if i.nil?
r << k
else
r << "#{k}=#{URI.encode_component i.to_s}"
end
end
r
end.join separator
end
private
def struct_of? struct
(keys - struct.members.map {|m| m.to_s }).size.zero?
end
end
class String
def ch?
/\A[+!][^ \007,]{1,50}\z/ === self
end
def screen_name?
/\A[A-Za-z0-9_]{1,15}\z/ === self
end
def encoding! enc
return self unless respond_to? :force_encoding
force_encoding enc
end
end
module URI::Escape
alias :_orig_escape :escape
if defined? ::RUBY_REVISION and RUBY_REVISION < 24544
# URI.escape("あ1") #=> "%E3%81%82\xEF\xBC\x91"
# URI("file:///4") #=> #
# "\\d" -> "[0-9]" for Ruby 1.9
def escape str, unsafe = %r{[^-_.!~*'()a-zA-Z0-9;/?:@&=+$,\[\]]}
_orig_escape(str, unsafe)
end
alias :encode :escape
end
def encode_component str, unsafe = /[^-_.!~*'()a-zA-Z0-9 ]/
_orig_escape(str, unsafe).tr(" ", "+")
end
def rstrip str
str.sub(%r{
(?: ( / [^/?#()]* (?: \( [^/?#()]* \) [^/?#()]* )* ) \) [^/?#()]*
| \.
) \z
}x, "\\1")
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16668,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
on("-n", "--name [user name or email address]") do |name|
opts[:name] = name
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
#def daemonize(foreground = false)
# [:INT, :TERM, :HUP].each do |sig|
# Signal.trap sig, "EXIT"
# end
# return yield if $DEBUG or foreground
# Process.fork do
# Process.setsid
# Dir.chdir "/"
# STDIN.reopen "/dev/null"
# STDOUT.reopen "/dev/null", "a"
# STDERR.reopen STDOUT
# yield
# end
# exit! 0
#end
#daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], TwitterIrcGateway, opts).start
#end
end
net-irc-0.0.9/examples/2ch.rb 0000755 0001750 0001750 00000011516 11604616526 015552 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$KCODE = "u" if RUBY_VERSION < "1.9" # json use this
require 'uri'
require 'net/http'
require 'stringio'
require 'zlib'
require 'nkf'
class ThreadData
class UnknownThread < StandardError; end
attr_accessor :uri
attr_accessor :last_modified, :size
Line = Struct.new(:n, :name, :mail, :misc, :body, :opts, :id) do
def aa?
body = self.body
return false if body.count("\n") < 3
significants = body.scan(/[>\n0-9a-z0-9A-Za-zA-Zぁ-んァ-ン一-龠]/u).size.to_f
body_length = body.scan(/./u).size
is_aa = 1 - significants / body_length
is_aa > 0.6
end
end
def initialize(thread_uri)
@uri = URI(thread_uri)
_, _, _, @board, @num, = *@uri.path.split('/')
@dat = []
end
def length
@dat.length
end
def subject
retrieve(true) if @dat.size.zero?
self[1].opts || ""
end
def [](n)
l = @dat[n - 1]
return nil unless l
name, mail, misc, body, opts = * l.split(/<>/)
id = misc[/ID:([^\s]+)/, 1]
body.gsub!(/ /, "\n")
body.gsub!(/<[^>]+>/, "")
body.gsub!(/^\s+|\s+$/, "")
body.gsub!(/&(gt|lt|amp|nbsp);/) {|s|
{ 'gt' => ">", 'lt' => "<", 'amp' => "&", 'nbsp' => " " }[$1]
}
Line.new(n, name, mail, misc, body, opts, id)
end
def dat
@num
end
def retrieve(force=false)
@dat = [] if @force
res = Net::HTTP.start(@uri.host, @uri.port) do |http|
req = Net::HTTP::Get.new('/%s/dat/%d.dat' % [@board, @num])
req['User-Agent'] = 'Monazilla/1.00 (2ig.rb/0.0e)'
req['Accept-Encoding'] = 'gzip' unless @size
unless force
req['If-Modified-Since'] = @last_modified if @last_modified
req['Range'] = "bytes=%d-" % @size if @size
end
http.request(req)
end
ret = nil
case res.code.to_i
when 200, 206
body = res.body
if res['Content-Encoding'] == 'gzip'
body = StringIO.open(body, 'rb') {|io| Zlib::GzipReader.new(io).read }
end
@last_modified = res['Last-Modified']
if res.code == '206'
@size += body.size
else
@size = body.size
end
body = NKF.nkf('-w', body)
curr = @dat.size + 1
@dat.concat(body.split(/\n/))
last = @dat.size
(curr..last).map {|n|
self[n]
}
when 416 # たぶん削除が発生
p ['416']
retrieve(true)
[]
when 304 # Not modified
[]
when 302 # dat 落ち
p ['302', res['Location']]
raise UnknownThread
else
p ['Unknown Status:', res.code]
[]
end
end
def canonicalize_subject(subject)
subject.gsub(/[A-Za-z0-9]/u) {|c|
c.unpack("U*").map {|i| i - 65248 }.pack("U*")
}
end
def guess_next_thread
res = Net::HTTP.start(@uri.host, @uri.port) do |http|
req = Net::HTTP::Get.new('/%s/subject.txt' % @board)
req['User-Agent'] = 'Monazilla/1.00 (2ig.rb/0.0e)'
http.request(req)
end
recent_posted_threads = (900..999).inject({}) {|r,i|
line = self[i]
line.body.scan(%r|ttp://#{@uri.host}/test/read.cgi/[^/]+/\d+/|).each do |uri|
r["h#{uri}"] = i
end if line
r
}
current_subject = canonicalize_subject(self.subject)
current_thread_rev = current_subject.scan(/\d+/).map {|d| d.to_i }
current = current_subject.scan(/./u)
body = NKF.nkf('-w', res.body)
threads = body.split(/\n/).map {|l|
dat, rest = *l.split(/<>/)
dat.sub!(/\.dat$/, "")
uri = "http://#{@uri.host}/test/read.cgi/#{@board}/#{dat}/"
subject, n = */(.+?) \((\d+)\)/.match(rest).captures
canonical_subject = canonicalize_subject(subject)
thread_rev = canonical_subject[/\d+/].to_i
distance = (dat == self.dat) ? Float::MAX :
(subject == self.subject) ? 0 :
levenshtein(canonical_subject.scan(/./u), current)
continuous_num = current_thread_rev.find {|rev| rev == thread_rev - 1 }
appear_recent = recent_posted_threads[uri]
score = distance
score -= 10 if continuous_num
score -= 10 if appear_recent
score += 10 if dat.to_i < self.dat.to_i
{
:uri => uri,
:dat => dat,
:subject => subject,
:distance => distance,
:continuous_num => continuous_num,
:appear_recent => appear_recent,
:score => score.to_f
}
}.sort_by {|o|
o[:score]
}
threads
end
def levenshtein(a, b)
case
when a.empty?
b.length
when b.empty?
a.length
when a == b
0
else
d = Array.new(a.length + 1) { |s|
Array.new(b.length + 1, 0)
}
(0..a.length).each do |i|
d[i][0] = i
end
(0..b.length).each do |j|
d[0][j] = j
end
(1..a.length).each do |i|
(1..b.length).each do |j|
cost = (a[i - 1] == b[j - 1]) ? 0 : 1
d[i][j] = [
d[i-1][j ] + 1,
d[i ][j-1] + 1,
d[i-1][j-1] + cost
].min
end
end
d[a.length][b.length]
end
end
end
if __FILE__ == $0
require 'pp'
thread = ThreadData.new(ARGV[0])
pp thread.guess_next_thread.reverse
p thread.subject
end
net-irc-0.0.9/examples/hatena-star-stream.rb 0000755 0001750 0001750 00000014700 11604616526 020574 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
=begin
## Licence
Ruby's by cho45
=end
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding # json use this
require "rubygems"
require "json"
require "net/irc"
require "mechanize"
require "sdbm"
require "tmpdir"
require "nkf"
require "hpricot"
WWW::Mechanize.html_parser = Hpricot
class HatenaStarStream < Net::IRC::Server::Session
def server_name
"hatenastarstream"
end
def server_version
"0.0.0"
end
def main_channel
"#star"
end
def initialize(*args)
super
@ua = WWW::Mechanize.new
@ua.max_history = 1
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts = @opts.inject({}) {|r,i|
key, value = i.split("=")
r.update(key => value)
}
start_observer
end
def on_disconnected
@observer.kill rescue nil
end
def on_privmsg(m)
@ua.instance_eval do
get "http://h.hatena.ne.jp/"
form = page.forms.find {|f| f.action == "/entry" }
form["body"] = m[1]
submit form
end
post server_name, NOTICE, main_channel, "posted"
rescue Exception => e
log e.inspect
end
def on_ctcp(target, message)
end
def on_whois(m)
end
def on_who(m)
end
def on_join(m)
end
def on_part(m)
end
private
def start_observer
@observer = Thread.start do
Thread.abort_on_exception = true
loop do
begin
login
@log.info "getting report..."
@ua.get("http://s.hatena.ne.jp/#{@real}/report")
entries = @ua.page.root.search("#main span.entry-title a").map {|a|
a['href']
}
@log.info "getting stars... #{entries.length}"
stars = retrive_stars(entries)
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
entries.reverse_each do |entry|
next if stars[entry].empty?
i = 0
s = stars[entry].select {|star|
id = "#{entry}::#{i}"
i += 1
if db.include?(id)
false
else
db[id] = "1"
true
end
}
post server_name, NOTICE, main_channel, "#{entry} #{title(entry)}" if s.length > 0
if @opts.key?("metadata")
post "metadata", NOTICE, main_channel, JSON.generate({ "uri" => entry })
end
s.each do |star|
post server_name, NOTICE, main_channel, "id:%s \x03%d%s%s\x030 %s" % [
star.name,
Star::Colors[star.color],
((star.color == "normal") ? "☆" : "★") * ([star.count, 10].min),
(star.count > 10) ? "(...#{star.count})" : "",
star.quote
]
end
end
rescue Exception => e
@log.error e.inspect
@log.error e.backtrace
ensure
db.close rescue nil
end
sleep 60 * 5
end
end
end
def retrive_stars(entries, n=0)
uri = "http://s.hatena.ne.jp/entries.json?"
while uri.length < 1800 and n < entries.length
uri << "uri=#{URI.escape(entries[n], /[^-.!~*'()\w]/n)}&"
n += 1
end
ret = JSON.load(@ua.get(uri).body)["entries"].inject({}) {|r,i|
if i["stars"].any? {|star| star.kind_of? Numeric }
i = JSON.load(@ua.get("http://s.hatena.ne.jp/entry.json?uri=#{URI.escape(i["uri"])}").body)["entries"].first
end
stars = []
if i["colored_stars"]
i["colored_stars"].each do |s|
s["stars"].each do |j|
stars << Star.new(j, s["color"])
end
end
end
i["stars"].each do |j|
star = Star.new(j)
if star.quote.empty? && stars.last && stars.last.name == star.name && stars.last.color == "normal"
stars.last.count += 1
else
stars << star
end
end
r.update(i["uri"] => stars)
}
if n < entries.length
ret.update retrive_stars(entries, n)
end
ret
end
def title(url)
uri = URI(url)
@ua.get(uri)
text = ""
case
when uri.fragment
fragment = @ua.page.root.at("//*[@name = '#{uri.fragment}']") || @ua.page.root.at("//*[@id = '#{uri.fragment}']")
text = fragment.inner_text + fragment.following.text + fragment.parent.following.text
when uri.to_s =~ %r|^http://h.hatena.ne.jp/[^/]+/\d+|
text = @ua.page.root.at("#main .entries .entry .list-body div.body").inner_text
else
text = @ua.page.root.at("//title").inner_text
end
text.gsub!(/\s+/, " ")
text.strip!
NKF.nkf("-w", text).split(//)[0..30].join
rescue Exception => e
@log.debug ["title:", e.inspect]
""
end
def login
@log.info "logging in as #{@real}"
@ua.get "https://www.hatena.ne.jp/login?backurl=http%3A%2F%2Fd.hatena.ne.jp%2F"
return if @ua.page.forms.empty?
form = @ua.page.forms.first
form["name"] = @real
form["password"] = @pass
@ua.submit(form)
unless @ua.page.forms.empty?
post server_name, ERR_PASSWDMISMATCH, ":Password incorrect"
finish
end
end
class Star < OpenStruct
Colors = {
"purple" => 6,
"blue" => 2,
"green" => 3,
"red" => 4,
"normal" => 8,
}
def initialize(obj, col="normal")
super(obj)
self.count = obj["count"].to_i + 1
self.color = col
end
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16702,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
trap("SIGINT") { exit! 0 }
trap("SIGTERM") { exit! 0 }
trap("SIGHUP") { exit! 0 }
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f
STDERR.reopen f
}
yield
end
exit! 0
end
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], HatenaStarStream, opts).start
end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/examples/echo_bot.rb 0000755 0001750 0001750 00000000670 11604616526 016657 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "rubygems"
require "net/irc"
require "pp"
class EchoBot < Net::IRC::Client
def initialize(*args)
super
end
def on_rpl_welcome(m)
post JOIN, "#bot_test"
end
def on_privmsg(m)
post NOTICE, m[0], m[1]
end
end
EchoBot.new("foobar", "6667", {
:nick => "foobartest",
:user => "foobartest",
:real => "foobartest",
}).start
net-irc-0.0.9/examples/iig.rb 0000755 0001750 0001750 00000050452 11604616526 015650 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$KCODE = "u" unless defined? ::Encoding # json use this
=begin
# iig.rb
Identi.ca/Laconi.ca IRC gateway
## Launch
$ ruby iig.rb
If you want to help:
$ ruby iig.rb --help
## Configuration
Options specified by after IRC realname.
Configuration example for Tiarra .
identica {
host: localhost
port: 16672
name: username@example.com athack tid ratio=77:1:12 replies
password: password on Identi.ca
in-encoding: utf8
out-encoding: utf8
}
### athack
If `athack` client option specified,
all nick in join message is leading with @.
So if you complemente nicks (e.g. Irssi),
it's good for Identi.ca like reply command (@nick).
In this case, you will see torrent of join messages after connected,
because NAMES list can't send @ leading nick (it interpreted op.)
### tid[=]
Apply ID to each message for make favorites by CTCP ACTION.
/me fav ID [ID...]
can be
0 => white
1 => black
2 => blue navy
3 => green
4 => red
5 => brown maroon
6 => purple
7 => orange olive
8 => yellow
9 => lightgreen lime
10 => teal
11 => lightcyan cyan aqua
12 => lightblue royal
13 => pink lightpurple fuchsia
14 => grey
15 => lightgrey silver
### jabber=:
If `jabber=:` option specified,
use jabber to get friends timeline.
You must setup im notifing settings in the site and
install "xmpp4r-simple" gem.
$ sudo gem install xmpp4r-simple
Be careful for managing password.
### alwaysim
Use IM instead of any APIs (e.g. post)
### ratio=:
### replies[=]
### maxlimit=
### checkrls=
## Feed
## License
Ruby's by cho45
=end
$LOAD_PATH << "lib" << "../lib"
require "rubygems"
require "net/irc"
require "net/http"
require "uri"
require "socket"
require "time"
require "logger"
require "yaml"
require "pathname"
require "cgi"
require "json"
class IdenticaIrcGateway < Net::IRC::Server::Session
def server_name; "identicagw" end
def server_version; "0.0.0" end
def main_channel; "#Identi.ca" end
def api_base; URI("http://identi.ca/api/") end
def api_source; "iig.rb" end
def jabber_bot_id; "update@identi.ca" end
def hourly_limit; 100 end
class APIFailed < StandardError; end
def initialize(*args)
super
@groups = {}
@channels = [] # joined channels (groups)
@user_agent = "#{self.class}/#{server_version} (#{File.basename(__FILE__)})"
@config = Pathname.new(ENV["HOME"]) + ".iig"
load_config
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts = @opts.inject({}) {|r,i|
key, value = i.split("=")
r.update(key => value)
}
@tmap = TypableMap.new
if @opts["jabber"]
jid, pass = @opts["jabber"].split(":", 2)
@opts["jabber"].replace("jabber=#{jid}:********")
if jabber_bot_id
begin
require "xmpp4r-simple"
start_jabber(jid, pass)
rescue LoadError
log "Failed to start Jabber."
log 'Installl "xmpp4r-simple" gem or check your ID/pass.'
finish
end
else
@opts.delete("jabber")
log "This gateway does not support Jabber bot."
end
end
log "Client Options: #{@opts.inspect}"
@log.info "Client Options: #{@opts.inspect}"
@hourly_limit = hourly_limit
@ratio = Struct.new(:timeline, :friends, :replies).new(*(@opts["ratio"] || "10:3").split(":").map {|ratio| ratio.to_f })
@ratio[:replies] = @opts.key?("replies") ? (@opts["replies"] || 5).to_f : 0.0
footing = @ratio.inject {|sum, ratio| sum + ratio }
@ratio.each_pair {|m, v| @ratio[m] = v / footing }
@timeline = []
@check_friends_thread = Thread.start do
loop do
begin
check_friends
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:friends])
end
end
return if @opts["jabber"]
sleep 3
@check_timeline_thread = Thread.start do
loop do
begin
check_timeline
# check_direct_messages
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:timeline])
end
end
return unless @opts.key?("replies")
sleep 10
@check_replies_thread = Thread.start do
loop do
begin
check_replies
rescue APIFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:replies])
end
end
end
def on_disconnected
@check_friends_thread.kill rescue nil
@check_replies_thread.kill rescue nil
@check_timeline_thread.kill rescue nil
@im_thread.kill rescue nil
@im.disconnect rescue nil
end
def on_privmsg(m)
return m.ctcps.each {|ctcp| on_ctcp(m[0], ctcp) } if m.ctcp?
retry_count = 3
ret = nil
target, message = *m.params
begin
if target =~ /^#/
if @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
ret = @im.deliver(jabber_bot_id, message)
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(message)
else
ret = api("statuses/update", {"status" => message})
end
else
# direct message
ret = api("direct_messages/new", {"user" => target, "text" => message})
end
raise APIFailed, "API failed" unless ret
log "Status Updated"
rescue => e
@log.error [retry_count, e.inspect].inspect
if retry_count > 0
retry_count -= 1
@log.debug "Retry to setting status..."
retry
else
log "Some Error Happened on Sending #{message}. #{e}"
end
end
end
def on_ctcp(target, message)
_, command, *args = message.split(/\s+/)
case command
when "list", "ls"
nick = args[0]
unless (1..200).include?(count = args[1].to_i)
count = 20
end
@log.debug([ nick, message ])
res = api("statuses/user_timeline", {"id" => nick, "count" => "#{count}"}).reverse_each do |s|
@log.debug(s)
post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
end
unless res
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
when /^(un)?fav(?:ou?rite)?$/
method, pfx = $1.nil? ? ["create", "F"] : ["destroy", "Unf"]
args.each_with_index do |tid, i|
st = @tmap[tid]
if st
sleep 1 if i > 0
res = api("favorites/#{method}/#{st["id"]}")
post server_name, NOTICE, main_channel, "#{pfx}av: #{res["user"]["screen_name"]}: #{res["text"]}"
else
post server_name, NOTICE, main_channel, "No such ID #{tid}"
end
end
when "link", "ln"
args.each do |tid|
st = @tmap[tid]
if st
st["link"] = "http://#{api_base.host}/notice/#{st["id"]}" unless st["link"]
post server_name, NOTICE, main_channel, st["link"]
else
post server_name, NOTICE, main_channel, "No such ID #{tid}"
end
end
# when /^ratios?$/
# if args[1].nil? ||
# @opts.key?("replies") && args[2].nil?
# return post server_name, NOTICE, main_channel, "/me ratios [ ]"
# end
# ratios = args.map {|ratio| ratio.to_f }
# if ratios.any? {|ratio| ratio <= 0.0 }
# return post server_name, NOTICE, main_channel, "Ratios must be greater than 0."
# end
# footing = ratios.inject {|sum, ratio| sum + ratio }
# @ratio[:timeline] = ratios[0]
# @ratio[:friends] = ratios[1]
# @ratio[:replies] = ratios[2] || 0.0
# @ratio.each_pair {|m, v| @ratio[m] = v / footing }
# intervals = @ratio.map {|ratio| freq ratio }
# post server_name, NOTICE, main_channel, "Intervals: #{intervals.join(", ")}"
when /^(?:de(?:stroy|l(?:ete)?)|remove|miss)$/
args.each_with_index do |tid, i|
st = @tmap[tid]
if st
sleep 1 if i > 0
res = api("statuses/destroy/#{st["id"]}")
post server_name, NOTICE, main_channel, "Destroyed: #{res["text"]}"
else
post server_name, NOTICE, main_channel, "No such ID #{tid}"
end
end
when "in", "location"
location = args.join(" ")
api("account/update_location", {"location" => location})
location = location.empty? ? "nowhere" : "in #{location}"
post server_name, NOTICE, main_channel, "You are #{location} now."
end
rescue APIFailed => e
log e.inspect
end; private :on_ctcp
def on_whois(m)
nick = m.params[0]
f = (@friends || []).find {|i| i["screen_name"] == nick }
if f
post server_name, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
post server_name, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
post server_name, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
else
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
def on_who(m)
channel = m.params[0]
case
when channel == main_channel
# "
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
# :"
@friends.each do |f|
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post server_name, RPL_ENDOFWHO, @nick, channel
when @groups.key?(channel)
@groups[channel].each do |name|
f = @friends.find {|i| i["screen_name"] == name }
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post server_name, RPL_ENDOFWHO, @nick, channel
else
post server_name, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
end
end
def on_join(m)
channels = m.params[0].split(/\s*,\s*/)
channels.each do |channel|
next if channel == main_channel
@channels << channel
@channels.uniq!
post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
post server_name, MODE, channel, "+o", @nick
save_config
end
end
def on_part(m)
channel = m.params[0]
return if channel == main_channel
@channels.delete(channel)
post @nick, PART, channel, "Ignore group #{channel}, but setting is alive yet."
end
def on_invite(m)
nick, channel = *m.params
return if channel == main_channel
if (@friends || []).find {|i| i["screen_name"] == nick }
((@groups[channel] ||= []) << nick).uniq!
post "#{nick}!#{nick}@#{api_base.host}", JOIN, channel
post server_name, MODE, channel, "+o", nick
save_config
else
post ERR_NOSUCHNICK, nil, nick, "No such nick/channel"
end
end
def on_kick(m)
channel, nick, mes = *m.params
return if channel == main_channel
if (@friends || []).find {|i| i["screen_name"] == nick }
(@groups[channel] ||= []).delete(nick)
post nick, PART, channel
save_config
else
post ERR_NOSUCHNICK, nil, nick, "No such nick/channel"
end
end
private
def check_timeline
api("statuses/friends_timeline", {:count => "117"}).reverse_each do |s|
id = s["id"]
next if id.nil? || @timeline.include?(id)
@timeline << id
nick = s["user"]["screen_name"]
mesg = generate_status_message(s)
tid = @tmap.push(s)
@log.debug [id, nick, mesg]
if nick == @nick # 自分のときは TOPIC に
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
else
if @opts.key?("tid")
mesg = "%s \x03%s [%s]" % [mesg, @opts["tid"] || 10, tid]
end
message(nick, main_channel, mesg)
end
@groups.each do |channel, members|
next unless members.include?(nick)
if @opts.key?("tid")
mesg = "%s \x03%s [%s]" % [mesg, @opts["tid"] || 10, tid]
end
message(nick, channel, mesg)
end
end
@log.debug "@timeline.size = #{@timeline.size}"
@timeline = @timeline.last(117)
end
def generate_status_message(status)
s = status
mesg = s["text"]
@log.debug(mesg)
# time = Time.parse(s["created_at"]) rescue Time.now
m = {""" => "\"", "<" => "<", ">" => ">", "&" => "&", "\n" => " "}
mesg.gsub!(/#{m.keys.join("|")}/) { m[$&] }
mesg
end
def check_replies
time = @prev_time_r || Time.now
@prev_time_r = Time.now
api("statuses/replies").reverse_each do |s|
id = s["id"]
next if id.nil? || @timeline.include?(id)
created_at = Time.parse(s["created_at"]) rescue next
next if created_at < time
nick = s["user"]["screen_name"]
mesg = generate_status_message(s)
tid = @tmap.push(s)
@log.debug [id, nick, mesg]
if @opts.key?("tid")
mesg = "%s \x03%s [%s]" % [mesg, @opts["tid"] || 10, tid]
end
message nick, main_channel, mesg
end
end
def check_direct_messages
time = @prev_time_d || Time.now
@prev_time_d = Time.now
api("direct_messages", {"since" => time.httpdate}).reverse_each do |s|
nick = s["sender_screen_name"]
mesg = s["text"]
time = Time.parse(s["created_at"])
@log.debug [nick, mesg, time].inspect
message(nick, @nick, mesg)
end
end
def check_friends
first = true unless @friends
@friends ||= []
friends = api("statuses/friends")
if first && !@opts.key?("athack")
@friends = friends
post server_name, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
else
prv_friends = @friends.map {|i| i["screen_name"] }
now_friends = friends.map {|i| i["screen_name"] }
# Twitter API bug?
return if !first && (now_friends.length - prv_friends.length).abs > 10
(now_friends - prv_friends).each do |join|
join = "@#{join}" if @opts.key?("athack")
post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
end
(prv_friends - now_friends).each do |part|
part = "@#{part}" if @opts.key?("athack")
post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
end
@friends = friends
end
end
def check_downtime
@prev_downtime ||= nil
schedule = api("help/downtime_schedule", {}, {:avoid_error => true})["error"]
if @prev_downtime != schedule && @prev_downtime = schedule
msg = schedule.gsub(%r{[\r\n]|}m, "")
uris = URI.extract(msg)
uris.each do |uri|
msg << " #{uri}"
end
msg.gsub!(/<[^>]+>/, "")
log "\002\037#{msg}\017"
# TODO: sleeping for the downtime
end
end
def freq(ratio)
max = (@opts["maxlimit"] || 100).to_i
limit = @hourly_limit < max ? @hourly_limit : max
f = 3600 / (limit * ratio).round
@log.debug "Frequency: #{f}"
f
end
def start_jabber(jid, pass)
@log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
@im = Jabber::Simple.new(jid, pass)
@im.add(jabber_bot_id)
@im_thread = Thread.start do
loop do
begin
@im.received_messages.each do |msg|
@log.debug [msg.from, msg.body]
if msg.from.strip == jabber_bot_id
# Twitter -> 'id: msg'
body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
if Regexp.last_match
nick, id = Regexp.last_match.captures
body = CGI.unescapeHTML(body)
message(id || nick, main_channel, body)
end
end
end
rescue Exception => e
@log.error "Error on Jabber loop: #{e.inspect}"
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 1
end
end
end
def save_config
config = {
:channels => @channels,
:groups => @groups,
}
@config.open("w") do |f|
YAML.dump(config, f)
end
end
def load_config
@config.open do |f|
config = YAML.load(f)
@channels = config[:channels]
@groups = config[:groups]
end
rescue Errno::ENOENT
end
def require_post?(path)
[
%r{^statuses/(?:update$|destroy/)},
"direct_messages/new",
"account/update_location",
%r{^favorites/},
].any? {|i| i === path }
end
def api(path, q = {}, opt = {})
ret = {}
headers = {"User-Agent" => @user_agent}
headers["If-Modified-Since"] = q["since"] if q.key?("since")
q["source"] ||= api_source
q = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" : r }.join("&")
path = path.sub(%r{^/+}, "")
uri = api_base.dup
uri.path += "#{path}.json"
if require_post? path
req = Net::HTTP::Post.new(uri.request_uri, headers)
req.body = q
else
uri.query = q
req = Net::HTTP::Get.new(uri.request_uri, headers)
end
req.basic_auth(@real, @pass)
@log.debug uri.inspect
ret = Net::HTTP.start(uri.host, uri.port) {|http| http.request(req) }
case ret
when Net::HTTPOK # 200
ret = JSON.parse(ret.body.gsub(/'(y(?:es)?|no?|true|false|null)'/, '"\1"'))
if ret.kind_of?(Hash) && !opt[:avoid_error] && ret["error"]
raise APIFailed, "Server Returned Error: #{ret["error"]}"
end
ret
when Net::HTTPNotModified # 304
[]
when Net::HTTPBadRequest # 400
# exceeded the rate limitation
raise APIFailed, "#{ret.code}: #{ret.message}"
else
raise APIFailed, "Server Returned #{ret.code} #{ret.message}"
end
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
raise APIFailed, e.inspect
end
def message(sender, target, str)
# str.gsub!(/(x)?([0-9a-f]+);/i) do
# [$1 ? $2.hex : $2.to_i].pack("U")
# end
str = untinyurl(str)
sender = "#{sender}!#{sender}@#{api_base.host}"
post sender, PRIVMSG, target, str
end
def log(str)
str.gsub!(/\r\n|[\r\n]/, " ")
post server_name, NOTICE, main_channel, str
end
def untinyurl(text)
text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
uri = URI(m)
uri.host = uri.host.sub($1, "") if $1
Net::HTTP.start(uri.host, uri.port) {|http|
http.open_timeout = 3
begin
http.head(uri.request_uri, {"User-Agent" => @user_agent})["Location"] || m
rescue Timeout::Error
m
end
}
}
end
class TypableMap < Hash
Roman = %w[
k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q
].unshift("").map do |consonant|
case consonant
when "y", /\A.{2}/ then %w|a u o|
when "q" then %w|a i e o|
else %w|a i u e o|
end.map {|vowel| "#{consonant}#{vowel}" }
end.flatten
def initialize(size = 1)
@seq = Roman
@n = 0
@size = size
end
def generate(n)
ret = []
begin
n, r = n.divmod(@seq.size)
ret << @seq[r]
end while n > 0
ret.reverse.join
end
def push(obj)
id = generate(@n)
self[id] = obj
@n += 1
@n %= @seq.size ** @size
id
end
alias << push
def clear
@n = 0
super
end
private :[]=
undef update, merge, merge!, replace
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16672,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
on("-n", "--name [user name or email address]") do |name|
opts[:name] = name
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
#def daemonize(foreground = false)
# trap("SIGINT") { exit! 0 }
# trap("SIGTERM") { exit! 0 }
# trap("SIGHUP") { exit! 0 }
# return yield if $DEBUG || foreground
# Process.fork do
# Process.setsid
# Dir.chdir "/"
# File.open("/dev/null") {|f|
# STDIN.reopen f
# STDOUT.reopen f
# STDERR.reopen f
# }
# yield
# end
# exit! 0
#end
#daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], IdenticaIrcGateway, opts).start
#end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/examples/hig.rb 0000755 0001750 0001750 00000045114 11604616526 015646 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
=begin
# hig.rb
## Launch
$ ruby hig.rb
If you want to help:
$ ruby hig.rb --help
## Configuration
Options specified by after irc realname.
Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
haiku {
host: localhost
port: 16679
name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=10:3:5
password: password on Haiku
in-encoding: utf8
out-encoding: utf8
}
### athack
If `athack` client option specified,
all nick in join message is leading with @.
So if you complemente nicks (e.g. Irssi),
it's good for Twitter like reply command (@nick).
In this case, you will see torrent of join messages after connected,
because NAMES list can't send @ leading nick (it interpreted op.)
### tid=
Apply id to each message for make favorites by CTCP ACTION.
/me fav id
can be
0 => white
1 => black
2 => blue navy
3 => green
4 => red
5 => brown maroon
6 => purple
7 => orange olive
8 => yellow
9 => lightgreen lime
10 => teal
11 => lightcyan cyan aqua
12 => lightblue royal
13 => pink lightpurple fuchsia
14 => grey
15 => lightgrey silver
### jabber=:
If `jabber=:` option specified,
use jabber to get friends timeline.
You must setup im notifing settings in the site and
install "xmpp4r-simple" gem.
$ sudo gem install xmpp4r-simple
Be careful for managing password.
### alwaysim
Use IM instead of any APIs (e.g. post)
### ratio=::
## License
Ruby's by cho45
=end
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding # json use this
require "rubygems"
require "net/irc"
require "net/http"
require "uri"
require "json"
require "socket"
require "time"
require "logger"
require "yaml"
require "pathname"
require "cgi"
require "digest/md5"
Net::HTTP.version_1_2
class HaikuIrcGateway < Net::IRC::Server::Session
def server_name
"haikugw"
end
def server_version
"0.0.0"
end
def main_channel
"#haiku"
end
def api_base
URI(ENV["HAIKU_BASE"] || "http://h.hatena.ne.jp/api/")
end
def api_source
"hig.rb"
end
def jabber_bot_id
nil
end
def hourly_limit
60
end
class ApiFailed < StandardError; end
def initialize(*args)
super
@channels = {}
@user_agent = "#{self.class}/#{server_version} (hig.rb)"
@counters = {} # for jabber fav
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts = @opts.inject({}) {|r,i|
key, value = i.split("=")
r.update(key => value)
}
@tmap = TypableMap.new
if @opts["jabber"]
jid, pass = @opts["jabber"].split(":", 2)
@opts["jabber"].replace("jabber=#{jid}:********")
if jabber_bot_id
begin
require "xmpp4r-simple"
start_jabber(jid, pass)
rescue LoadError
log "Failed to start Jabber."
log 'Installl "xmpp4r-simple" gem or check your id/pass.'
finish
end
else
@opts.delete("jabber")
log "This gateway does not support Jabber bot."
end
end
log "Client Options: #{@opts.inspect}"
@log.info "Client Options: #{@opts.inspect}"
timeline_ratio, friends_ratio, channel_ratio = (@opts["ratio"] || "10:3:5").split(":").map {|ratio| ratio.to_i }
footing = (timeline_ratio + friends_ratio + channel_ratio).to_f
@timeline = []
@check_follows_thread = Thread.start do
loop do
begin
check_friends
check_keywords
rescue ApiFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(friends_ratio / footing)
end
end
return if @opts["jabber"]
@check_timeline_thread = Thread.start do
sleep 10
loop do
begin
check_timeline
rescue ApiFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(timeline_ratio / footing)
end
end
end
def on_disconnected
@check_follows_thread.kill rescue nil
@check_timeline_thread.kill rescue nil
@im_thread.kill rescue nil
@im.disconnect rescue nil
end
def on_privmsg(m)
return m.ctcps.each {|ctcp| on_ctcp(m[0], ctcp) } if m.ctcp?
retry_count = 3
ret = nil
target, message = *m.params
begin
channel = target.sub(/^#/, "")
reply = message[/\s+>(.+)$/, 1]
if !reply && @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
message = "##{channel} #{message}" unless "##{channel}" == main_channel
ret = @im.deliver(jabber_bot_id, message)
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, message
else
channel = "" if "##{channel}" == main_channel
rid = rid_for(reply) if reply
if @typo
log "typo mode. requesting..."
message.gsub!(/\\n/, "\n")
file = Net::HTTP.get(URI("http://lab.lowreal.net/test/haiku.rb/?text=" + URI.escape(message)))
ret = api("statuses/update", {"file" => file, "in_reply_to_status_id" => rid, "keyword" => channel})
else
ret = api("statuses/update", {"status" => message, "in_reply_to_status_id" => rid, "keyword" => channel})
end
log "Status Updated via API"
end
raise ApiFailed, "API failed" unless ret
check_timeline
rescue => e
@log.error [retry_count, e.message, e.inspect, e.backtrace].inspect
if retry_count > 0
retry_count -= 1
@log.debug "Retry to setting status..."
# retry
else
log "Some Error Happened on Sending #{message}. #{e}"
end
end
end
def on_ctcp(target, message)
_, command, *args = message.split(/\s+/)
case command
when "list"
nick = args[0]
@log.debug([ nick, message ])
res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
@log.debug(s)
post nick, NOTICE, main_channel, s
end
unless res
post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
when "fav"
target = args[0]
st = @tmap[target]
id = rid_for(target)
if st || id
unless id
if @im && @im.connected?
# IM のときはいろいろめんどうなことする
nick, count = *st
pos = @counters[nick] - count
@log.debug "%p %s %d/%d => %d" % [
st,
nick,
count,
@counters[nick],
pos
]
res = api("statuses/user_timeline", { "id" => nick })
raise ApiFailed, "#{nick} may be private mode" if res.empty?
if res[pos]
id = res[pos]["id"]
else
raise ApiFailed, "#{pos} of #{nick} is not found."
end
else
id = st["id"]
end
end
res = api("favorites/create/#{id}", {})
post nil, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"].gsub(URI.regexp(%w|http https|), "http...")}"
else
post nil, NOTICE, main_channel, "No such id or status #{target}"
end
when "link"
tid = args[0]
st = @tmap[tid]
if st
st["link"] = (api_base + "/#{st["user"]["screen_name"]}/#{st["id"]}").to_s unless st["link"]
post nil, NOTICE, main_channel, st["link"]
else
post nil, NOTICE, main_channel, "No such id #{tid}"
end
when "typo"
@typo = !@typo
post nil, NOTICE, main_channel, "typo mode: #{@typo}"
end
rescue ApiFailed => e
log e.inspect
end; private :on_ctcp
def on_whois(m)
nick = m.params[0]
f = (@friends || []).find {|i| i["screen_name"] == nick }
if f
post nil, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
post nil, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
post nil, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
post nil, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
else
post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
def on_join(m)
return ### なんかしらんけど何度も入ってしまってうざいので……
channels = m.params[0].split(/\s*,\s*/)
channels.each do |channel|
next if channel == main_channel
begin
api("keywords/create/#{URI.escape(channel.sub(/^#/, ""))}")
@channels[channel] = {
:read => []
}
post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
rescue => e
@log.debug e.inspect
post nil, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
end
def on_part(m)
channel = m.params[0]
return if channel == main_channel
@channels.delete(channel)
api("keywords/destroy/#{URI.escape(channel.sub(/^#/, ""))}")
post "#{@nick}!#{@nick}@#{api_base.host}", PART, channel
end
def on_who(m)
channel = m.params[0]
case
when channel == main_channel
# "
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
# :"
@friends.each do |f|
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post nil, RPL_ENDOFWHO, @nick, channel
when @groups.key?(channel)
@groups[channel].each do |name|
f = @friends.find {|i| i["screen_name"] == name }
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post nil, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post nil, RPL_ENDOFWHO, @nick, channel
else
post nil, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
end
end
private
def check_timeline
api("statuses/friends_timeline").reverse_each do |s|
begin
id = s["id"]
next if id.nil? || @timeline.include?(id)
@timeline << id
nick = s["user"]["id"]
mesg = generate_status_message(s)
tid = @tmap.push(s)
@log.debug [id, nick, mesg]
channel = "##{s["keyword"]}"
case
when s["keyword"].match(/^id:/)
channel = main_channel
when !@channels.keys.include?(channel)
channel = main_channel
mesg = "%s = %s" % [s["keyword"], mesg]
end
if nick == @nick # 自分のときは topic に
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, mesg
else
if @opts["tid"]
message(nick, channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
else
message(nick, channel, "%s" % [mesg, tid])
end
if @opts.key?("metadata")
post "metadata", NOTICE, channel, JSON.generate({ "uri" => (api_base + "/#{s["user"]["screen_name"]}/#{s["id"]}").to_s })
end
end
rescue => e
@log.debug "Error: %p" % e
end
end
@log.debug "@timeline.size = #{@timeline.size}"
@timeline = @timeline.last(100)
end
def generate_status_message(s)
mesg = s["text"]
mesg.sub!("#{s["keyword"]}=", "") unless s["keyword"] =~ /^id:/
mesg << " > #{s["in_reply_to_user_id"]}" unless s["in_reply_to_user_id"].empty?
@log.debug(mesg)
mesg
end
def check_friends
first = true unless @friends
@friends ||= []
friends = api("statuses/friends")
if first && !@opts.key?("athack")
@friends = friends
post nil, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
post nil, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
else
prv_friends = @friends.map {|i| i["screen_name"] }
now_friends = friends.map {|i| i["screen_name"] }
# Twitter API bug?
return if !first && (now_friends.length - prv_friends.length).abs > 10
(now_friends - prv_friends).each do |join|
join = "@#{join}" if @opts.key?("athack")
post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
end
(prv_friends - now_friends).each do |part|
part = "@#{part}" if @opts.key?("athack")
post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
end
@friends = friends
end
end
def check_keywords
keywords = api("statuses/keywords").map {|i| "##{i["title"]}" }
current = @channels.keys
current.delete main_channel
(current - keywords).each do |part|
@channels.delete(part)
post "#{@nick}!#{@nick}@#{api_base.host}", PART, part
end
(keywords - current).each do |join|
@channels[join] = {
:read => []
}
post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, join
end
end
def freq(ratio)
ret = 3600 / (hourly_limit * ratio).round
@log.debug "Frequency: #{ret}"
ret
end
def start_jabber(jid, pass)
@log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
@im = Jabber::Simple.new(jid, pass)
@im.add(jabber_bot_id)
@im_thread = Thread.start do
loop do
begin
@im.received_messages.each do |msg|
@log.debug [msg.from, msg.body]
if msg.from.strip == jabber_bot_id
# Haiku -> 'nick(id): msg'
body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
if Regexp.last_match
nick, id = Regexp.last_match.captures
body = CGI.unescapeHTML(body)
case
when nick == "投稿完了"
log "#{nick}: #{body}"
when nick == "チャンネル投稿完了"
log "#{nick}: #{body}"
when body =~ /^#([a-z_]+)\s+(.+)$/i
# channel message or not
message(id || nick, "##{Regexp.last_match[1]}", Regexp.last_match[2])
when nick == "photo" && body =~ %r|^http://haiku\.jp/user/([^/]+)/|
nick = Regexp.last_match[1]
message(nick, main_channel, body)
else
@counters[nick] ||= 0
@counters[nick] += 1
tid = @tmap.push([nick, @counters[nick]])
message(nick, main_channel, "%s \x03%s [%s]" % [body, @opts["tid"], tid])
end
end
end
end
rescue Exception => e
@log.error "Error on Jabber loop: #{e.inspect}"
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 1
end
end
end
def require_post?(path)
[
%r|/update|,
%r|/create|,
%r|/destroy|,
].any? {|i| i === path }
end
def api(path, q={})
ret = {}
q["source"] ||= api_source
uri = api_base.dup
uri.path = "/api/#{path}.json"
uri.query = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^:,-.!~*'()\w]/n)}" : r }.join("&")
req = nil
if require_post?(path)
req = Net::HTTP::Post.new(uri.path)
if q["file"]
boundary = (rand(0x1_00_00_00_00_00) + 0x1_00_00_00_00_00).to_s(16)
@log.info boundary
req["content-type"] = "multipart/form-data; boundary=#{boundary}"
body = ""
q.each do |k, v|
body << "--#{boundary}\r\n"
if k == "file"
body << "Content-Disposition: form-data; name=\"#{k}\"; filename=\"temp.png\";\r\n"
body << "Content-Transfer-Encoding: binary\r\n"
body << "Content-Type: image/png\r\n"
else
body << "Content-Disposition: form-data; name=\"#{k}\";\r\n"
end
body << "\r\n"
body << v.to_s
body << "\r\n"
end
body << "--#{boundary}--\r\n"
req.body = body
uri.query = ""
else
req.body = uri.query
end
else
req = Net::HTTP::Get.new(uri.request_uri)
end
req.basic_auth(@real, @pass)
req["User-Agent"] = @user_agent
req["If-Modified-Since"] = q["since"] if q.key?("since")
@log.debug uri.inspect
ret = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
case ret
when Net::HTTPOK # 200
ret = JSON.parse(ret.body.gsub(/:'/, ':"').gsub(/',/, '",').gsub(/'(y(?:es)?|no?|true|false|null)'/, '"\1"'))
raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
ret
when Net::HTTPNotModified # 304
[]
when Net::HTTPBadRequest # 400
# exceeded the rate limitation
raise ApiFailed, "#{ret.code}: #{ret.message}"
else
raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
end
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
raise ApiFailed, e.inspect
end
def message(sender, target, str)
sender = "#{sender}!#{sender}@#{api_base.host}"
post sender, PRIVMSG, target, str.gsub(/\s+/, " ")
end
def log(str)
str.gsub!(/\n/, " ")
post server_name, NOTICE, main_channel, str
end
# return rid of most recent matched status with text
def rid_for(text)
target = Regexp.new(Regexp.quote(text.strip), "i")
status = api("statuses/friends_timeline").find {|i|
next false if i["user"]["name"] == @nick # 自分は除外
i["text"] =~ target
}
@log.debug "Looking up status contains #{text.inspect} -> #{status.inspect}"
status ? status["id"] : nil
end
class TypableMap < Hash
Roman = %w[
k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q
].unshift("").map do |consonant|
case consonant
when "y", /\A.{2}/ then %w|a u o|
when "q" then %w|a i e o|
else %w|a i u e o|
end.map {|vowel| "#{consonant}#{vowel}" }
end.flatten
def initialize(size = 1)
@seq = Roman
@n = 0
@size = size
end
def generate(n)
ret = []
begin
n, r = n.divmod(@seq.size)
ret << @seq[r]
end while n > 0
ret.reverse.join
end
def push(obj)
id = generate(@n)
self[id] = obj
@n += 1
@n %= @seq.size ** @size
id
end
alias << push
def clear
@n = 0
super
end
private :[]=
undef update, merge, merge!, replace
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16679,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
on("-n", "--name [user name or email address]") do |name|
opts[:name] = name
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
# def daemonize(foreground=false)
# trap("SIGINT") { exit! 0 }
# trap("SIGTERM") { exit! 0 }
# trap("SIGHUP") { exit! 0 }
# return yield if $DEBUG || foreground
# Process.fork do
# Process.setsid
# Dir.chdir "/"
# File.open("/dev/null") {|f|
# STDIN.reopen f
# STDOUT.reopen f
# STDERR.reopen f
# }
# yield
# end
# exit! 0
# end
# daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], HaikuIrcGateway, opts).start
# end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/examples/ircd.rb 0000755 0001750 0001750 00000017566 11604616526 016032 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
require 'rubygems'
require 'net/irc'
class NetIrcServer < Net::IRC::Server::Session
def server_name
"net-irc"
end
def server_version
"0.0.0"
end
def available_user_modes
"iosw"
end
def default_user_modes
""
end
def available_channel_modes
"om"
end
def default_channel_modes
""
end
def initialize(*args)
super
@@channels ||= {}
@@users ||= {}
@ping = false
end
def on_pass(m)
end
def on_user(m)
@user, @real = m.params[0], m.params[3]
@host = @socket.peeraddr[2]
@prefix = Prefix.new("#{@nick}!#{@user}@#{@host}")
@joined_on = @updated_on = Time.now.to_i
post @socket, @prefix, NICK, nick
@nick = nick
@prefix = "#{@nick}!#{@user}@#{@host}"
time = Time.now.to_i
@@users[@nick.downcase] = {
:nick => @nick,
:user => @user,
:host => @host,
:real => @real,
:prefix => @prefix,
:socket => @socket,
:joined_on => time,
:updated_on => time
}
initial_message
start_ping
end
def on_join(m)
channels = m.params[0].split(/\s*,\s*/)
password = m.params[1]
channels.each do |channel|
unless channel.downcase =~ /^#/
post @socket, server_name, ERR_NOSUCHCHANNEL, @nick, channel, "No such channel"
next
end
unless @@channels.key?(channel.downcase)
channel_create(channel)
else
return if @@channels[channel.downcase][:users].key?(@nick.downcase)
@@channels[channel.downcase][:users][@nick.downcase] = []
end
mode = @@channels[channel.downcase][:mode].empty? ? "" : "+" + @@channels[channel.downcase][:mode]
post @socket, server_name, RPL_CHANNELMODEIS, @nick, @@channels[channel.downcase][:alias], mode
channel_users = ""
@@channels[channel.downcase][:users].each do |nick, m|
post @@users[nick][:socket], @prefix, JOIN, @@channels[channel.downcase][:alias]
case
when m.index("@")
f = "@"
when m.index("+")
f = "+"
else
f = ""
end
channel_users += "#{f}#{@@users[nick.downcase][:nick]} "
end
post @socket, server_name, RPL_NAMREPLY, @@users[nick][:nick], "=", @@channels[channel.downcase][:alias], "#{channel_users.strip}"
post @socket, server_name, RPL_ENDOFNAMES, @@users[nick][:nick], @@channels[channel.downcase][:alias], "End of /NAMES list"
end
end
def on_part(m)
channel, message = *m.params
@@channels[channel.downcase][:users].each do |nick, f|
post @@users[nick][:socket], @prefix, PART, @@channels[channel.downcase][:alias], message
end
channel_part(channel)
end
def on_quit(m)
message = m.params[0]
@@channels.each do |channel, f|
if f[:users].key?(@nick.downcase)
channel_part(channel)
f[:users].each do |nick, m|
post @@users[nick][:socket], @prefix, QUIT, message
end
end
end
finish
end
def on_disconnected
super
@@channels.each do |channel, f|
if f[:users].key?(@nick.downcase)
channel_part(channel)
f[:users].each do |nick, m|
post @@users[nick][:socket], @prefix, QUIT, "disconnect"
end
end
end
channel_part_all
@@users.delete(@nick.downcase)
end
def on_who(m)
channel = m.params[0]
return unless channel
c = channel.downcase
case
when @@channels.key?(c)
@@channels[c][:users].each do |nickname, m|
nick = @@users[nickname][:nick]
user = @@users[nickname][:user]
host = @@users[nickname][:host]
real = @@users[nickname][:real]
case
when m.index("@")
f = "@"
when m.index("+")
f = "+"
else
f = ""
end
post @socket, server_name, RPL_WHOREPLY, @nick, @@channels[c][:alias], user, host, server_name, nick, "H#{f}", "0 #{real}"
end
post @socket, server_name, RPL_ENDOFWHO, @nick, @@channels[c][:alias], "End of /WHO list"
end
end
def on_mode(m)
end
def on_privmsg(m)
while (Time.now.to_i - @updated_on < 2)
sleep 2
end
idle_update
return on_ctcp(m[0], ctcp_decoding(m[1])) if m.ctcp?
target, message = *m.params
t = target.downcase
case
when @@channels.key?(t)
if @@channels[t][:users].key?(@nick.downcase)
@@channels[t][:users].each do |nick, m|
post @@users[nick][:socket], @prefix, PRIVMSG, @@channels[t][:alias], message unless nick == @nick.downcase
end
else
post @socket, nil, ERR_CANNOTSENDTOCHAN, @nick, target, "Cannot send to channel"
end
when @@users.key?(t)
post @@users[nick][:socket], @prefix, PRIVMSG, @@users[t][:nick], message
else
post @socket, nil, ERR_NOSUCHNICK, @nick, target, "No such nick/channel"
end
end
def on_ping(m)
post @socket, server_name, PONG, m.params[0]
end
def on_pong(m)
@ping = true
end
def idle_update
@updated_on = Time.now.to_i
if logged_in?
@@users[@nick.downcase][:updated_on] = @updated_on
end
end
def channel_create(channel)
@@channels[channel.downcase] = {
:alias => channel,
:topic => "",
:mode => default_channel_modes,
:users => {@nick.downcase => ["@"]},
}
end
def channel_part(channel)
@@channels[channel.downcase][:users].delete(@nick.downcase)
channel_delete(channel.downcase) if @@channels[channel.downcase][:users].size == 0
end
def channel_part_all
@@channels.each do |c|
channel_part(c)
end
end
def channel_delete(channel)
@@channels.delete(channel.downcase)
end
def post(socket, prefix, command, *params)
m = Message.new(prefix, command, params.map{|s|
s.gsub(/[\r\n]/, "")
})
socket << m
rescue
finish
end
def start_ping
Thread.start do
loop do
@ping = false
time = Time.now.to_i
if @ping == false && (time - @updated_on > 60)
post @socket, server_name, PING, @prefix
loop do
sleep 1
if @ping
break
end
if 60 < Time.now.to_i - time
Thread.stop
finish
end
end
end
sleep 60
end
end
end
# Call when client connected.
# Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
def initial_message
post @socket, server_name, RPL_WELCOME, @nick, "Welcome to the Internet Relay Network #{@prefix}"
post @socket, server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
post @socket, server_name, RPL_CREATED, @nick, "This server was created #{Time.now}"
post @socket, server_name, RPL_MYINFO, @nick, "#{server_name} #{server_version} #{available_user_modes} #{available_channel_modes}"
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 6969,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
on("-n", "--name [user name or email address]") do |name|
opts[:name] = name
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
#def daemonize(foreground = false)
# [:INT, :TERM, :HUP].each do |sig|
# Signal.trap sig, "EXIT"
# end
# return yield if $DEBUG or foreground
# Process.fork do
# Process.setsid
# Dir.chdir "/"
# STDIN.reopen "/dev/null"
# STDOUT.reopen "/dev/null", "a"
# STDERR.reopen STDOUT
# yield
# end
# exit! 0
#end
#daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], NetIrcServer, opts).start
#end
end
net-irc-0.0.9/examples/wig.rb 0000755 0001750 0001750 00000050657 11604616526 015675 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
=begin
# wig.rb
wig.rb channel: http://wassr.jp/channel/wigrb
## Launch
$ ruby wig.rb
If you want to help:
$ ruby wig.rb --help
## Configuration
Options specified by after irc realname.
Configuration example for Tiarra ( http://coderepos.org/share/wiki/Tiarra ).
wassr {
host: localhost
port: 16670
name: username@example.com athack jabber=username@example.com:jabberpasswd tid=10 ratio=10:3:5
password: password on Wassr
in-encoding: utf8
out-encoding: utf8
}
### athack
If `athack` client option specified,
all nick in join message is leading with @.
So if you complemente nicks (e.g. Irssi),
it's good for Twitter like reply command (@nick).
In this case, you will see torrent of join messages after connected,
because NAMES list can't send @ leading nick (it interpreted op.)
### tid=
Apply id to each message for make favorites by CTCP ACTION.
/me fav id
can be
0 => white
1 => black
2 => blue navy
3 => green
4 => red
5 => brown maroon
6 => purple
7 => orange olive
8 => yellow
9 => lightgreen lime
10 => teal
11 => lightcyan cyan aqua
12 => lightblue royal
13 => pink lightpurple fuchsia
14 => grey
15 => lightgrey silver
### jabber=:
If `jabber=:` option specified,
use jabber to get friends timeline.
You must setup im notifing settings in the site and
install "xmpp4r-simple" gem.
$ sudo gem install xmpp4r-simple
Be careful for managing password.
### alwaysim
Use IM instead of any APIs (e.g. post)
### ratio=::
## License
Ruby's by cho45
=end
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding # json use this
require "rubygems"
require "net/irc"
require "net/http"
require "uri"
require "json"
require "socket"
require "time"
require "logger"
require "yaml"
require "pathname"
require "cgi"
require "digest/md5"
Net::HTTP.version_1_2
class WassrIrcGateway < Net::IRC::Server::Session
def server_name
"wassrgw"
end
def server_version
"0.0.0"
end
def main_channel
"#wassr"
end
def api_base
URI("http://api.wassr.jp/")
end
def api_source
"wig.rb"
end
def jabber_bot_id
"wassr-bot@wassr.jp"
end
def hourly_limit
60
end
class ApiFailed < StandardError; end
def initialize(*args)
super
@channels = {}
@user_agent = "#{self.class}/#{server_version} (wig.rb)"
@counters = {} # for jabber fav
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts = @opts.inject({}) {|r,i|
key, value = i.split("=")
r.update(key => value)
}
@tmap = TypableMap.new
if @opts["jabber"]
jid, pass = @opts["jabber"].split(":", 2)
@opts["jabber"].replace("jabber=#{jid}:********")
if jabber_bot_id
begin
require "xmpp4r-simple"
start_jabber(jid, pass)
rescue LoadError
log "Failed to start Jabber."
log 'Installl "xmpp4r-simple" gem or check your id/pass.'
finish
end
else
@opts.delete("jabber")
log "This gateway does not support Jabber bot."
end
end
log "Client Options: #{@opts.inspect}"
@log.info "Client Options: #{@opts.inspect}"
@ratio = Struct.new(:timeline, :friends, :channel).new(*(@opts["ratio"] || "10:3:5").split(":").map {|ratio| ratio.to_f })
@footing = @ratio.inject {|r,i| r + i }
@timeline = []
@check_friends_thread = Thread.start do
loop do
begin
check_friends
rescue ApiFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:friends] / @footing)
end
end
return if @opts["jabber"]
@check_timeline_thread = Thread.start do
sleep 3
loop do
begin
check_timeline
# check_direct_messages
rescue ApiFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:timeline] / @footing)
end
end
@check_channel_thread = Thread.start do
sleep 5
Thread.abort_on_exception= true
loop do
begin
check_channel
# check_direct_messages
rescue ApiFailed => e
@log.error e.inspect
rescue Exception => e
@log.error e.inspect
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep freq(@ratio[:channel] / @footing)
end
end
end
def on_disconnected
@check_friends_thread.kill rescue nil
@check_timeline_thread.kill rescue nil
@check_channel_thread.kill rescue nil
@im_thread.kill rescue nil
@im.disconnect rescue nil
end
def on_privmsg(m)
return m.ctcps.each {|ctcp| on_ctcp(m[0], ctcp) } if m.ctcp?
retry_count = 3
ret = nil
target, message = *m.params
begin
if target =~ /^#(.+)/
channel = Regexp.last_match[1]
reply = message[/\s+>(.+)$/, 1]
reply = reply.force_encoding("UTF-8") if reply && reply.respond_to?(:force_encoding)
if @utf7
message = Iconv.iconv("UTF-7", "UTF-8", message).join
message = message.force_encoding("ASCII-8BIT") if message.respond_to?(:force_encoding)
end
if !reply && @opts.key?("alwaysim") && @im && @im.connected? # in jabber mode, using jabber post
message = "##{channel} #{message}" unless "##{channel}" == main_channel
ret = @im.deliver(jabber_bot_id, message)
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, channel, untinyurl(message)
else
if "##{channel}" == main_channel
rid = rid_for(reply) if reply
ret = api("statuses/update", {"status" => message, "reply_status_rid" => rid})
else
ret = api("channel_message/update", {"name_en" => channel, "body" => message})
end
log "Status Updated via API"
end
else
# direct message
ret = api("direct_messages/new", {"user" => target, "text" => message})
end
raise ApiFailed, "API failed" unless ret
rescue => e
@log.error [retry_count, e.inspect].inspect
if retry_count > 0
retry_count -= 1
@log.debug "Retry to setting status..."
retry
else
log "Some Error Happened on Sending #{message}. #{e}"
end
end
end
def on_ctcp(target, message)
_, command, *args = message.split(/\s+/)
case command
when "utf7"
begin
require "iconv"
@utf7 = !@utf7
log "utf7 mode: #{@utf7 ? 'on' : 'off'}"
rescue LoadError => e
log "Can't load iconv."
end
when "list"
nick = args[0]
@log.debug([ nick, message ])
res = api("statuses/user_timeline", { "id" => nick }).reverse_each do |s|
@log.debug(s)
post nick, NOTICE, main_channel, "#{generate_status_message(s)}"
end
unless res
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
when "fav"
target = args[0]
st = @tmap[target]
id = rid_for(target)
if st || id
unless id
if @im && @im.connected?
# IM のときはいろいろめんどうなことする
nick, count = *st
pos = @counters[nick] - count
@log.debug "%p %s %d/%d => %d" % [
st,
nick,
count,
@counters[nick],
pos
]
res = api("statuses/user_timeline", { "id" => nick })
raise ApiFailed, "#{nick} may be private mode" if res.empty?
if res[pos]
id = res[pos]["rid"]
else
raise ApiFailed, "#{pos} of #{nick} is not found."
end
else
id = st["id"] || st["rid"]
end
end
res = api("favorites/create/#{id}", {})
post server_name, NOTICE, main_channel, "Fav: #{res["screen_name"]}: #{res["text"]}"
else
post server_name, NOTICE, main_channel, "No such id or status #{target}"
end
when "link"
tid = args[0]
st = @tmap[tid]
if st
st["link"] = (api_base + "/#{st["user"]["screen_name"]}/statuses/#{st["id"]}").to_s unless st["link"]
post server_name, NOTICE, main_channel, st["link"]
else
post server_name, NOTICE, main_channel, "No such id #{tid}"
end
end
rescue ApiFailed => e
log e.inspect
end; private :on_ctcp
def on_whois(m)
nick = m.params[0]
f = (@friends || []).find {|i| i["screen_name"] == nick }
if f
post server_name, RPL_WHOISUSER, @nick, nick, nick, api_base.host, "*", "#{f["name"]} / #{f["description"]}"
post server_name, RPL_WHOISSERVER, @nick, nick, api_base.host, api_base.to_s
post server_name, RPL_WHOISIDLE, @nick, nick, "0", "seconds idle"
post server_name, RPL_ENDOFWHOIS, @nick, nick, "End of WHOIS list"
else
post server_name, ERR_NOSUCHNICK, nick, "No such nick/channel"
end
end
def on_join(m)
channels = m.params[0].split(/\s*,\s*/)
channels.each do |channel|
next if channel == main_channel
res = api("channel/exists", { "name_en" => channel.sub(/^#/, "") })
if res["exists"]
@channels[channel] = {
:read => []
}
post "#{@nick}!#{@nick}@#{api_base.host}", JOIN, channel
else
post server_name, ERR_NOSUCHNICK, channel, "No such nick/channel"
end
end
end
def on_part(m)
channel = m.params[0]
return if channel == main_channel
@channels.delete(channel)
post "#{@nick}!#{@nick}@#{api_base.host}", PART, channel
end
def on_who(m)
channel = m.params[0]
case
when channel == main_channel
# "
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
# :"
@friends.each do |f|
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post server_name, RPL_ENDOFWHO, @nick, channel
when @groups.key?(channel)
@groups[channel].each do |name|
f = @friends.find {|i| i["screen_name"] == name }
user = nick = f["screen_name"]
host = serv = api_base.host
real = f["name"]
post server_name, RPL_WHOREPLY, @nick, channel, user, host, serv, nick, "H*@", "0 #{real}"
end
post server_name, RPL_ENDOFWHO, @nick, channel
else
post server_name, ERR_NOSUCHNICK, @nick, nick, "No such nick/channel"
end
end
private
def check_timeline
@prev_time ||= Time.at(0)
api("statuses/friends_timeline", {"since" => @prev_time.httpdate}).reverse_each do |s|
id = s["id"] || s["rid"]
next if id.nil? || @timeline.include?(id)
@timeline << id
nick = s["user_login_id"]
mesg = generate_status_message(s)
tid = @tmap.push(s)
@log.debug [id, nick, mesg]
if nick == @nick # 自分のときは topic に
post "#{nick}!#{nick}@#{api_base.host}", TOPIC, main_channel, untinyurl(mesg)
else
if @opts["tid"]
message(nick, main_channel, "%s \x03%s [%s]" % [mesg, @opts["tid"], tid])
else
message(nick, main_channel, "%s" % [mesg, tid])
end
end
end
@log.debug "@timeline.size = #{@timeline.size}"
@timeline = @timeline.last(100)
@prev_time = Time.now
end
def check_channel
@channels.keys.each do |channel|
@log.debug "getting channel -> #{channel}..."
api("channel_message/list", { "name_en" => channel.sub(/^#/, "") }).reverse_each do |s|
begin
id = Digest::MD5.hexdigest(s["user"]["login_id"] + s["body"])
next if @channels[channel][:read].include?(id)
@channels[channel][:read] << id
nick = s["user"]["login_id"]
mesg = s["body"]
if nick == @nick
post nick, NOTICE, channel, mesg
else
message(nick, channel, mesg)
end
rescue Execepton => e
post server_name, NOTICE, channel, e.inspect
end
end
@channels[channel][:read] = @channels[channel][:read].last(100)
end
end
def generate_status_message(status)
s = status
mesg = s["text"]
@log.debug(mesg)
begin
require 'iconv'
mesg = mesg.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
mesg = "[utf7]: #{mesg}" if mesg =~ /[^a-z0-9\s]/i
rescue LoadError
rescue Iconv::IllegalSequence
end
# added @user in no use @user reply message (Wassr only)
if s.has_key?("reply_status_url") and s["reply_status_url"] and s["text"] !~ /^@.*/ and %r{([^/]+)/statuses/[^/]+}.match(s["reply_status_url"])
reply_user_id = $1
mesg = "@#{reply_user_id} #{mesg}"
end
# display area name (Wassr only)
if s.has_key?("areaname") and s["areaname"]
mesg += " L: #{s["areaname"]}"
end
# display photo URL (Wassr only)
if s.has_key?("photo_url") and s["photo_url"]
mesg += " #{s["photo_url"]}"
end
# time = Time.parse(s["created_at"]) rescue Time.now
m = { """ => "\"", "<"=> "<", ">"=> ">", "&"=> "&", "\n" => " "}
mesg.gsub!(/(#{m.keys.join("|")})/) { m[$1] }
mesg
end
def check_direct_messages
@prev_time_d ||= Time.now
api("direct_messages", {"since" => @prev_time_d.httpdate}).reverse_each do |s|
nick = s["sender_screen_name"]
mesg = s["text"]
time = Time.parse(s["created_at"])
@log.debug [nick, mesg, time].inspect
message(nick, @nick, mesg)
end
@prev_time_d = Time.now
end
def check_friends
first = true unless @friends
@friends ||= []
friends = []
1.upto(5) do |i|
f = api("statuses/friends", {"page" => i.to_s})
friends += f
break if f.length < 100
end
if first && !@opts.key?("athack")
@friends = friends
post server_name, RPL_NAMREPLY, @nick, "=", main_channel, @friends.map{|i| "@#{i["screen_name"]}" }.join(" ")
post server_name, RPL_ENDOFNAMES, @nick, main_channel, "End of NAMES list"
else
prv_friends = @friends.map {|i| i["screen_name"] }
now_friends = friends.map {|i| i["screen_name"] }
# Twitter API bug?
return if !first && (now_friends.length - prv_friends.length).abs > 10
(now_friends - prv_friends).each do |join|
join = "@#{join}" if @opts.key?("athack")
post "#{join}!#{join}@#{api_base.host}", JOIN, main_channel
end
(prv_friends - now_friends).each do |part|
part = "@#{part}" if @opts.key?("athack")
post "#{part}!#{part}@#{api_base.host}", PART, main_channel, ""
end
@friends = friends
end
end
def freq(ratio)
ret = 3600 / (hourly_limit * ratio).round
@log.debug "Frequency: #{ret}"
ret
end
def start_jabber(jid, pass)
@log.info "Logging-in with #{jid} -> jabber_bot_id: #{jabber_bot_id}"
@im = Jabber::Simple.new(jid, pass)
@im.add(jabber_bot_id)
@im_thread = Thread.start do
loop do
begin
@im.received_messages.each do |msg|
@log.debug [msg.from, msg.body]
if msg.from.strip == jabber_bot_id
# Wassr -> 'nick(id): msg'
body = msg.body.sub(/^(.+?)(?:\((.+?)\))?: /, "")
if Regexp.last_match
nick, id = Regexp.last_match.captures
body = CGI.unescapeHTML(body)
begin
require 'iconv'
body = body.sub(/^.+ > |^.+/) {|str| Iconv.iconv("UTF-8", "UTF-7", str).join }
body = "[utf7]: #{body}" if body =~ /[^a-z0-9\s]/i
rescue LoadError
rescue Iconv::IllegalSequence
end
case
when nick == "投稿完了"
log "#{nick}: #{body}"
when nick == "チャンネル投稿完了"
log "#{nick}: #{body}"
when body =~ /^#([a-z_]+)\s+(.+)$/i
# channel message or not
message(id || nick, "##{Regexp.last_match[1]}", Regexp.last_match[2])
when nick == "photo" && body =~ %r|^http://wassr\.jp/user/([^/]+)/|
nick = Regexp.last_match[1]
message(nick, main_channel, body)
else
@counters[nick] ||= 0
@counters[nick] += 1
tid = @tmap.push([nick, @counters[nick]])
message(nick, main_channel, "%s \x03%s [%s]" % [body, @opts["tid"], tid])
end
end
end
end
rescue Exception => e
@log.error "Error on Jabber loop: #{e.inspect}"
e.backtrace.each do |l|
@log.error "\t#{l}"
end
end
sleep 1
end
end
end
def require_post?(path)
[
"statuses/update",
"direct_messages/new",
"channel_message/update",
%r|^favorites/create|,
].any? {|i| i === path }
end
def api(path, q={})
ret = {}
q["source"] ||= api_source
uri = api_base.dup
uri.path = "/#{path}.json"
uri.query = q.inject([]) {|r,(k,v)| v ? r << "#{k}=#{URI.escape(v, /[^-.!~*'()\w]/n)}" : r }.join("&")
req = nil
if require_post?(path)
req = Net::HTTP::Post.new(uri.path)
req.body = uri.query
else
req = Net::HTTP::Get.new(uri.request_uri)
end
req.basic_auth(@real, @pass)
req["User-Agent"] = @user_agent
req["If-Modified-Since"] = q["since"] if q.key?("since")
@log.debug uri.inspect
ret = Net::HTTP.start(uri.host, uri.port) { |http| http.request(req) }
case ret
when Net::HTTPOK # 200
ret = JSON.parse(ret.body)
raise ApiFailed, "Server Returned Error: #{ret["error"]}" if ret.kind_of?(Hash) && ret["error"]
ret
when Net::HTTPNotModified # 304
[]
when Net::HTTPBadRequest # 400
# exceeded the rate limitation
raise ApiFailed, "#{ret.code}: #{ret.message}"
else
raise ApiFailed, "Server Returned #{ret.code} #{ret.message}"
end
rescue Errno::ETIMEDOUT, JSON::ParserError, IOError, Timeout::Error, Errno::ECONNRESET => e
raise ApiFailed, e.inspect
end
def message(sender, target, str)
str = untinyurl(str)
sender = "#{sender}!#{sender}@#{api_base.host}"
post sender, PRIVMSG, target, str
end
def log(str)
str.gsub!(/\n/, " ")
post server_name, NOTICE, main_channel, str
end
def untinyurl(text)
text.gsub(%r|http://(preview\.)?tinyurl\.com/[0-9a-z=]+|i) {|m|
uri = URI(m)
uri.host = uri.host.sub($1, "") if $1
expanded = Net::HTTP.start(uri.host, uri.port) {|http|
http.open_timeout = 3
begin
http.head(uri.request_uri, { "User-Agent" => @user_agent })["Location"] || m
rescue Timeout::Error
m
end
}
expanded = URI(expanded)
if %w|http https|.include? expanded.scheme
expanded.to_s
else
"#{expanded.scheme}: #{uri}"
end
}
end
# return rid of most recent matched status with text
def rid_for(text)
target = Regexp.new(Regexp.quote(text.strip), "i")
status = api("statuses/friends_timeline").find {|i|
next false if i["user_login_id"] == @nick # 自分は除外
i["text"] =~ target
}
@log.debug "Looking up status contains #{text.inspect} -> #{status.inspect}"
status ? status["rid"] : nil
end
class TypableMap < Hash
Roman = %w[
k g ky gy s z sh j t d ch n ny h b p hy by py m my y r ry w v q
].unshift("").map do |consonant|
case consonant
when "y", /\A.{2}/ then %w|a u o|
when "q" then %w|a i e o|
else %w|a i u e o|
end.map {|vowel| "#{consonant}#{vowel}" }
end.flatten
def initialize(size = 1)
@seq = Roman
@n = 0
@size = size
end
def generate(n)
ret = []
begin
n, r = n.divmod(@seq.size)
ret << @seq[r]
end while n > 0
ret.reverse.join
end
def push(obj)
id = generate(@n)
self[id] = obj
@n += 1
@n %= @seq.size ** @size
id
end
alias << push
def clear
@n = 0
super
end
private :[]=
undef update, merge, merge!, replace
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16670,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
on("-n", "--name [user name or email address]") do |name|
opts[:name] = name
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
# def daemonize(foreground=false)
# trap("SIGINT") { exit! 0 }
# trap("SIGTERM") { exit! 0 }
# trap("SIGHUP") { exit! 0 }
# return yield if $DEBUG || foreground
# Process.fork do
# Process.setsid
# Dir.chdir "/"
# File.open("/dev/null") {|f|
# STDIN.reopen f
# STDOUT.reopen f
# STDERR.reopen f
# }
# yield
# end
# exit! 0
# end
# daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], WassrIrcGateway, opts).start
# end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/examples/gmail.rb 0000755 0001750 0001750 00000007606 11604616526 016174 0 ustar uwabami uwabami #!/usr/bin/env ruby
# vim:encoding=UTF-8:
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
$KCODE = "u" unless defined? ::Encoding
require "rubygems"
require "net/irc"
require "sdbm"
require "tmpdir"
require "uri"
require "mechanize"
require "rexml/document"
class GmailNotifier < Net::IRC::Server::Session
def server_name
"gmail"
end
def server_version
"0.0.0"
end
def main_channel
"#gmail"
end
def initialize(*args)
super
@agent = WWW::Mechanize.new
end
def on_user(m)
super
post @prefix, JOIN, main_channel
post server_name, MODE, main_channel, "+o", @prefix.nick
@real, *@opts = @opts.name || @real.split(/\s+/)
@opts ||= []
start_observer
end
def on_disconnected
@observer.kill rescue nil
end
def on_privmsg(m)
super
case m[1]
when 'list'
check_mail
end
end
def on_ctcp(target, message)
end
def on_whois(m)
end
def on_who(m)
end
def on_join(m)
end
def on_part(m)
end
private
def start_observer
@observer = Thread.start do
Thread.abort_on_exception = true
loop do
begin
@agent.auth(@real, @pass)
page = @agent.get(URI.parse("https://gmail.google.com/gmail/feed/atom"))
feed = REXML::Document.new page.body
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
feed.get_elements('/feed/entry').reverse.each do |item|
id = item.text('id')
if db.include?(id)
next
else
db[id] = "1"
end
post server_name, PRIVMSG, main_channel, "Subject: #{item.text('title')} From: #{item.text('author/name')}"
post server_name, PRIVMSG, main_channel, "#{item.text('summary')}"
end
rescue Exception => e
@log.error e.inspect
ensure
db.close rescue nil
end
sleep 60 * 5
end
end
end
def check_mail
begin
@agent.auth(@real, @pass)
page = @agent.get(URI.parse("https://gmail.google.com/gmail/feed/atom"))
feed = REXML::Document.new page.body
db = SDBM.open("#{Dir.tmpdir}/#{@real}.db", 0666)
feed.get_elements('/feed/entry').reverse.each do |item|
id = item.text('id')
if db.include?(id)
#next
else
db[id] = "1"
end
post server_name, PRIVMSG, main_channel, "Subject: #{item.text('title')} From: #{item.text('author/name')}"
post server_name, PRIVMSG, main_channel, "#{item.text('summary')}"
end
rescue Exception => e
@log.error e.inspect
ensure
db.close rescue nil
end
end
end
if __FILE__ == $0
require "optparse"
opts = {
:port => 16800,
:host => "localhost",
:log => nil,
:debug => false,
:foreground => false,
}
OptionParser.new do |parser|
parser.instance_eval do
self.banner = <<-EOB.gsub(/^\t+/, "")
Usage: #{$0} [opts]
EOB
separator ""
separator "Options:"
on("-p", "--port [PORT=#{opts[:port]}]", "port number to listen") do |port|
opts[:port] = port
end
on("-h", "--host [HOST=#{opts[:host]}]", "host name or IP address to listen") do |host|
opts[:host] = host
end
on("-l", "--log LOG", "log file") do |log|
opts[:log] = log
end
on("--debug", "Enable debug mode") do |debug|
opts[:log] = $stdout
opts[:debug] = true
end
on("-f", "--foreground", "run foreground") do |foreground|
opts[:log] = $stdout
opts[:foreground] = true
end
parse!(ARGV)
end
end
opts[:logger] = Logger.new(opts[:log], "daily")
opts[:logger].level = opts[:debug] ? Logger::DEBUG : Logger::INFO
def daemonize(foreground=false)
trap("SIGINT") { exit! 0 }
trap("SIGTERM") { exit! 0 }
trap("SIGHUP") { exit! 0 }
return yield if $DEBUG || foreground
Process.fork do
Process.setsid
Dir.chdir "/"
File.open("/dev/null") {|f|
STDIN.reopen f
STDOUT.reopen f
STDERR.reopen f
}
yield
end
exit! 0
end
daemonize(opts[:debug] || opts[:foreground]) do
Net::IRC::Server.new(opts[:host], opts[:port], GmailNotifier, opts).start
end
end
# Local Variables:
# coding: utf-8
# End:
net-irc-0.0.9/README 0000644 0001750 0001750 00000003175 11604616526 013612 0 ustar uwabami uwabami
= net-irc
== Description
IRC library. This is mostly conform to RFC 1459 but partly not for convenience.
== Installation
=== Archive Installation
rake install
=== Gem Installation
gem install net-irc
== Features/Problems
* IRC client (for bot)
* IRC server (for gateway to webservices)
== Synopsis
=== Client
require "net/irc"
class SimpleClient < Net::IRC::Client
def on_privmsg(m)
super
channel, message = *m
if message =~ /Hello/
post NOTICE, channel, "Hello!"
end
end
end
Net::IRC::Client manages channel status and the information is set in @channels.
So, be careful to use @channels instance variable and call super surely.
=== Server
see example/tig.rb
== IRC Gateways
There are some gateways connecting to webservices.
* Twitter
* Wassr
* Hatena Haiku
* Hatena Star
If you want to run it, type following:
$ cd `ruby -rubygems -e 'print Gem.searcher.find("net/irc").full_gem_path+"/examples"'`
Twitter:
$ ./tig.rb -f >> /dev/null 2>&1
Wassr:
$ ./wig.rb
Run as daemon in default. If you want to help:
$ ./tig.rb --help
Usage: tig.rb [opts]
Options:
-p, --port [PORT=16668] port number to listen
-h, --host [HOST=localhost] host name or IP address to listen
-l, --log LOG log file
--debug Enable debug mode
-f, --foreground run foreground
-n [user name or email address]
--name
== Copyright
This library is based on RICE written by akira yamada.
Author:: cho45
Copyright:: Copyright (c) 2008-2009 cho45
License:: Ruby's
net-irc-0.0.9/ChangeLog 0000644 0001750 0001750 00000003242 11604616526 014477 0 ustar uwabami uwabami 2009-10-11 SATOH Hiroh
* [new]
Implemented Server#sessions which returns all sessions connected to
the server.
* Released 0.0.9
2009-08-08 SATOH Hiroh
* [bug]:
Fixed to work on ruby1.9.1 (now can send iso-2022-jp)
* [new]
Implemented Message#ctcps returns embedded all ctcp messages (drry).
* Released 0.0.8
2009-02-19 SATOH Hiroh
* [bug]:
Fixed net/irc.rb permission.
* Released 0.0.7
2009-02-01 SATOH Hiroh
* [bug]:
Fixed to work on ruby1.9.1
* [release]:
Released 0.0.6
2008-07-06 SATOH Hiroh
* [interface]:
Removed around @channels and separeted to
Net::IRC::Client::ChannelManager as just a sample of managing
channels.
* [release]:
Released 0.0.5
2008-07-06 Satoshi Nakagawa
* [new]:
Added a mode parser which can be configured automatically from 005 replies.
2008-06-28 cho45
* [interface]:
Change mode character to symbol.
* [new]:
Seperate each class to some files.
* [release]:
Released 0.0.4
2008-06-14 cho45
* [bug]:
Fixed examples. (twitter, wassr, lingr gateways)
* [release]:
Released 0.0.3
2008-02-06 cho45
* [release] @5832:
Released 0.0.2
2008-02-01 cho45
* [bug] @5986:
Fixed to destroy closed stream.
2008-01-31 cho45
* [new] @5939:
Added client example.
* [new] @5929:
Updated tests.
Made allow lame prefix in RPL_WELCOME (like freenode)
2008-01-29 cho45
* [bug] @5846:
athack つかわないときの処理がもろに間違ってた。
* [bug] @5843:
Net::IRC::Server の修正に追従できていなかった
* [release] @5832:
Released 0.0.1
net-irc-0.0.9/spec/ 0000775 0001750 0001750 00000000000 11604616526 013660 5 ustar uwabami uwabami net-irc-0.0.9/spec/modeparser_spec.rb 0000644 0001750 0001750 00000014272 11604616526 017364 0 ustar uwabami uwabami
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "net/irc"
include Net::IRC
include Constants
describe Message::ModeParser do
it "should parse RFC2812+ correctly" do
parser = Message::ModeParser.new
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
parser.parse("#oulu +R")[:positive].should == [[:R, nil]]
parser.parse("#oulu +R *!*@*.edu")[:positive].should == [[:R, "*!*@*.edu"]]
parser.parse("#foo +ooo foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
:negative => [],
}
parser.parse("#foo +oo-o foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"]],
:negative => [[:o, "baz"]],
}
parser.parse("#foo -oo+o foo bar baz").should == {
:positive => [[:o, "baz"]],
:negative => [[:o, "foo"], [:o, "bar"]],
}
parser.parse("#foo +imv foo").should == {
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
:negative => [],
}
parser.parse("#foo +lk 10 foo").should == {
:positive => [[:l, "10"], [:k, "foo"]],
:negative => [],
}
parser.parse("#foo -l+k foo").should == {
:positive => [[:k, "foo"]],
:negative => [[:l, nil]],
}
parser.parse("#foo +ao foo").should == {
:positive => [[:a, nil], [:o, "foo"]],
:negative => [],
}
end
it "should parse modes of Hyperion ircd correctly" do
parser = Message::ModeParser.new
parser.set(:CHANMODES, 'bdeIq,k,lfJD,cgijLmnPQrRstz')
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
parser.parse("#foo +ooo foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
:negative => [],
}
parser.parse("#foo +oo-o foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"]],
:negative => [[:o, "baz"]],
}
parser.parse("#foo -oo+o foo bar baz").should == {
:positive => [[:o, "baz"]],
:negative => [[:o, "foo"], [:o, "bar"]],
}
parser.parse("#foo +imv foo").should == {
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
:negative => [],
}
parser.parse("#foo +lk 10 foo").should == {
:positive => [[:l, "10"], [:k, "foo"]],
:negative => [],
}
parser.parse("#foo -l+k foo").should == {
:positive => [[:k, "foo"]],
:negative => [[:l, nil]],
}
parser.parse("#foo +cv foo").should == {
:positive => [[:c, nil], [:v, "foo"]],
:negative => [],
}
end
it "should parse modes of Unreal ircd correctly" do
parser = Message::ModeParser.new
parser.set(:PREFIX, '(qaohv)~&@%+')
parser.set(:CHANMODES, 'beI,kfL,lj,psmntirRcOAQKVCuzNSMTG')
parser.parse("#Finish +im")[:positive].should == [[:i, nil], [:m, nil]]
parser.parse("#Finish +o Kilroy")[:positive].should == [[:o, "Kilroy"]]
parser.parse("#Finish +v Kilroy")[:positive].should == [[:v, "Kilroy"]]
parser.parse("#Fins -s")[:negative].should == [[:s, nil]]
parser.parse("#42 +k oulu")[:positive].should == [[:k, "oulu"]]
parser.parse("#eu-opers +l 10")[:positive].should == [[:l, "10"]]
parser.parse("&oulu +b")[:positive].should == [[:b, nil]]
parser.parse("&oulu +b *!*@*")[:positive].should == [[:b, "*!*@*"]]
parser.parse("&oulu +b *!*@*.edu")[:positive].should == [[:b, "*!*@*.edu"]]
parser.parse("#oulu +e")[:positive].should == [[:e, nil]]
parser.parse("#oulu +e *!*@*.edu")[:positive].should == [[:e, "*!*@*.edu"]]
parser.parse("#oulu +I")[:positive].should == [[:I, nil]]
parser.parse("#oulu +I *!*@*.edu")[:positive].should == [[:I, "*!*@*.edu"]]
parser.parse("#foo +ooo foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"], [:o, "baz"]],
:negative => [],
}
parser.parse("#foo +oo-o foo bar baz").should == {
:positive => [[:o, "foo"], [:o, "bar"]],
:negative => [[:o, "baz"]],
}
parser.parse("#foo -oo+o foo bar baz").should == {
:positive => [[:o, "baz"]],
:negative => [[:o, "foo"], [:o, "bar"]],
}
parser.parse("#foo +imv foo").should == {
:positive => [[:i, nil], [:m, nil], [:v, "foo"]],
:negative => [],
}
parser.parse("#foo +lk 10 foo").should == {
:positive => [[:l, "10"], [:k, "foo"]],
:negative => [],
}
parser.parse("#foo -l+k foo").should == {
:positive => [[:k, "foo"]],
:negative => [[:l, nil]],
}
parser.parse("#foo -q+ah foo bar baz").should == {
:positive => [[:a, "bar"], [:h, "baz"]],
:negative => [[:q, "foo"]],
}
parser.parse("#foo +Av foo").should == {
:positive => [[:A, nil], [:v, "foo"]],
:negative => [],
}
end
end
net-irc-0.0.9/spec/net-irc_spec.rb 0000755 0001750 0001750 00000021306 11604616526 016563 0 ustar uwabami uwabami #!spec
# coding: ASCII-8BIT
# vim:encoding=UTF-8:
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "rubygems"
require "spec"
require "net/irc"
include Net::IRC
include Constants
describe Net::IRC::Message, "construct" do
it "should generate message correctly" do
m = Message.new("foo", "PRIVMSG", ["#channel", "message"])
m.to_s.should == ":foo PRIVMSG #channel message\r\n"
m = Message.new("foo", "PRIVMSG", ["#channel", "message with space"])
m.to_s.should == ":foo PRIVMSG #channel :message with space\r\n"
m = Message.new(nil, "PRIVMSG", ["#channel", "message"])
m.to_s.should == "PRIVMSG #channel message\r\n"
m = Message.new(nil, "PRIVMSG", ["#channel", "message with space"])
m.to_s.should == "PRIVMSG #channel :message with space\r\n"
m = Message.new(nil, "MODE", [
"#channel",
"+ooo",
"nick1",
"nick2",
"nick3"
])
m.to_s.should == "MODE #channel +ooo nick1 nick2 nick3\r\n"
m = Message.new(nil, "KICK", [
"#channel,#channel1",
"nick1,nick2",
])
m.to_s.should == "KICK #channel,#channel1 nick1,nick2\r\n"
end
it "should have ctcp? method" do
m = Message.new("foo", "PRIVMSG", ["#channel", "\x01ACTION foo\x01"])
m.ctcp?.should be_true
end
it "should behave as Array contains params" do
m = Message.new("foo", "PRIVMSG", ["#channel", "message"])
m[0].should == m.params[0]
m[1].should == m.params[1]
m.to_a.should == ["#channel", "message"]
channel, message = *m
channel.should == "#channel"
message.should == "message"
end
it "#to_a should return duplicated array" do
m = Message.new("foo", "PRIVMSG", ["#channel", "message"])
m[0].should == m.params[0]
m[1].should == m.params[1]
m.to_a.should == ["#channel", "message"]
m.to_a.clear
m.to_a.should == ["#channel", "message"]
end
end
describe Net::IRC::Message, "parse" do
it "should parse correctly following RFC." do
m = Message.parse("PRIVMSG #channel message\r\n")
m.prefix.should == ""
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "message"]
m = Message.parse("PRIVMSG #channel :message leading :\r\n")
m.prefix.should == ""
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "message leading :"]
m = Message.parse("PRIVMSG #channel middle :message leading :\r\n")
m.prefix.should == ""
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "middle", "message leading :"]
m = Message.parse("PRIVMSG #channel middle message with middle\r\n")
m.prefix.should == ""
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "middle", "message", "with", "middle"]
m = Message.parse(":prefix PRIVMSG #channel message\r\n")
m.prefix.should == "prefix"
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "message"]
m = Message.parse(":prefix PRIVMSG #channel :message leading :\r\n")
m.prefix.should == "prefix"
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "message leading :"]
end
it "should allow multibyte " do
m = Message.parse(":てすと PRIVMSG #channel :message leading :\r\n")
m.prefix.should == "てすと"
m.command.should == "PRIVMSG"
m.params.should == ["#channel", "message leading :"]
end
it "should allow space at end" do
m = Message.parse("JOIN #foobar \r\n")
m.prefix.should == ""
m.command.should == "JOIN"
m.params.should == ["#foobar"]
end
end
describe Net::IRC::Constants, "lookup" do
it "should lookup numeric replies from Net::IRC::COMMANDS" do
welcome = Net::IRC::Constants.const_get("RPL_WELCOME")
welcome.should == "001"
Net::IRC::COMMANDS[welcome].should == "RPL_WELCOME"
end
end
describe Net::IRC::Prefix, "" do
it "should be kind of String" do
Prefix.new("").should be_kind_of(String)
end
it "should parse prefix correctly." do
prefix = Prefix.new("foo!bar@localhost")
prefix.extract.should == ["foo", "bar", "localhost"]
prefix = Prefix.new("foo!-bar@localhost")
prefix.extract.should == ["foo", "-bar", "localhost"]
prefix = Prefix.new("foo!+bar@localhost")
prefix.extract.should == ["foo", "+bar", "localhost"]
prefix = Prefix.new("foo!~bar@localhost")
prefix.extract.should == ["foo", "~bar", "localhost"]
end
it "should allow multibyte in nick." do
prefix = Prefix.new("あああ!~bar@localhost")
prefix.extract.should == ["あああ", "~bar", "localhost"]
end
it "should allow lame prefix." do
prefix = Prefix.new("nick")
prefix.extract.should == ["nick", nil, nil]
end
it "has nick method" do
prefix = Prefix.new("foo!bar@localhost")
prefix.nick.should == "foo"
end
it "has user method" do
prefix = Prefix.new("foo!bar@localhost")
prefix.user.should == "bar"
end
it "has host method" do
prefix = Prefix.new("foo!bar@localhost")
prefix.host.should == "localhost"
end
end
describe Net::IRC, "utilities" do
it "has ctcp_encode method" do
message = ctcp_encode "ACTION hehe"
message.should == "\x01ACTION hehe\x01"
message = ctcp_encode "ACTION \x01 \x5c "
message.should == "\x01ACTION \x5c\x61 \x5c\x5c \x01"
message = ctcp_encode "ACTION \x00 \x0a \x0d \x10 "
message.should == "\x01ACTION \x100 \x10n \x10r \x10\x10 \x01"
end
it "has ctcp_decode method" do
message = ctcp_decode "\x01ACTION hehe\x01"
message.should == "ACTION hehe"
message = ctcp_decode "\x01ACTION \x5c\x61 \x5c\x5c \x01"
message.should == "ACTION \x01 \x5c "
message = ctcp_decode "\x01ACTION \x100 \x10n \x10r \x10\x10 \x01"
message.should == "ACTION \x00 \x0a \x0d \x10 "
end
end
class TestServerSession < Net::IRC::Server::Session
@@testq = SizedQueue.new(1)
@@instance = nil
def self.testq
@@testq
end
def self.instance
@@instance
end
def initialize(*args)
super
@@instance = self
end
def on_message(m)
@@testq << m
end
end
class TestClient < Net::IRC::Client
@@testq = SizedQueue.new(1)
def self.testq
@@testq
end
def on_message(m)
@@testq << m
end
end
describe Net::IRC, "server and client" do
before :all do
@port = nil
@server, @client = nil, nil
Thread.abort_on_exception = true
@tserver = Thread.start do
@server = Net::IRC::Server.new("localhost", @port, TestServerSession, {
:logger => Logger.new(nil),
})
@server.start
end
Thread.pass
true until @server.instance_variable_get(:@serv)
@port = @server.instance_variable_get(:@serv).addr[1]
@tclient = Thread.start do
@client = TestClient.new("localhost", @port, {
:nick => "foonick",
:user => "foouser",
:real => "foo real name",
:pass => "foopass",
:logger => Logger.new(nil),
})
@client.start
end
Thread.pass
true until @client
end
server_q = TestServerSession.testq
client_q = TestClient.testq
it "client should send pass/nick/user sequence." do
server_q.pop.to_s.should == "PASS foopass\r\n"
server_q.pop.to_s.should == "NICK foonick\r\n"
server_q.pop.to_s.should == "USER foouser 0 * :foo real name\r\n"
end
it "server should send 001,002,003 numeric replies." do
client_q.pop.to_s.should match(/^:net-irc 001 foonick :Welcome to the Internet Relay Network \S+!\S+@\S+/)
client_q.pop.to_s.should match(/^:net-irc 002 foonick :Your host is .+?, running version /)
client_q.pop.to_s.should match(/^:net-irc 003 foonick :This server was created /)
end
it "client posts PRIVMSG and server receives it." do
@client.instance_eval do
post PRIVMSG, "#channel", "message a b c"
end
message = server_q.pop
message.should be_a_kind_of(Net::IRC::Message)
message.to_s.should == "PRIVMSG #channel :message a b c\r\n"
end
if defined? Encoding
it "dummy encoding: client posts PRIVMSG and server receives it." do
@client.instance_eval do
s = "てすと".force_encoding("UTF-8")
post PRIVMSG, "#channel", s
end
message = server_q.pop
message.should be_a_kind_of(Net::IRC::Message)
message.to_s.should == "PRIVMSG #channel てすと\r\n"
end
it "dummy encoding: client posts PRIVMSG and server receives it." do
@client.instance_eval do
s = "てすと".force_encoding("UTF-8")
s.encode!("ISO-2022-JP")
post PRIVMSG, "#channel", s
end
message = server_q.pop
message.should be_a_kind_of(Net::IRC::Message)
message.to_s.should == "PRIVMSG #channel \e$B$F$9$H\e(B\r\n"
end
end
it "should allow lame RPL_WELCOME (not prefix but nick)" do
client = @client
TestServerSession.instance.instance_eval do
Thread.exclusive do
post "server", RPL_WELCOME, client.prefix.nick, "Welcome to the Internet Relay Network #{client.prefix.nick}"
post nil, NOTICE, "#test", "sep1"
end
end
Thread.pass
true until client_q.pop.to_s == "NOTICE #test sep1\r\n"
client.prefix.should == "foonick"
end
# it "should destroy closed session" do
# end
after :all do
@server.finish
@client.finish
@tserver.kill
@tclient.kill
@server = @client = @tserver = @tclient = nil
end
end
net-irc-0.0.9/spec/spec.opts 0000644 0001750 0001750 00000000010 11604616526 015506 0 ustar uwabami uwabami --color
net-irc-0.0.9/spec/channel_manager_spec.rb 0000755 0001750 0001750 00000012175 11604616526 020330 0 ustar uwabami uwabami #!spec
$LOAD_PATH << "lib"
$LOAD_PATH << "../lib"
require "rubygems"
require "spec"
require "thread"
require "net/irc"
require "net/irc/client/channel_manager"
include Net::IRC
include Constants
class ChannelManagerTestServerSession < Net::IRC::Server::Session
@@testq = SizedQueue.new(1)
@@instance = nil
def self.testq
@@testq
end
def self.instance
@@instance
end
def initialize(*args)
super
@@instance = self
end
def on_message(m)
@@testq << m
end
end
class ChannelManagerTestClient < Net::IRC::Client
include Net::IRC::Client::ChannelManager
@@testq = SizedQueue.new(1)
def self.testq
@@testq
end
def on_message(m)
@@testq << m
end
end
describe Net::IRC, "server and client" do
before :all do
@port = nil
@server, @client = nil, nil
Thread.abort_on_exception = true
Thread.start do
@server = Net::IRC::Server.new("localhost", @port, ChannelManagerTestServerSession, {
:logger => Logger.new(nil),
})
@server.start
end
Thread.pass
true until @server.instance_variable_get(:@serv)
@port = @server.instance_variable_get(:@serv).addr[1]
Thread.start do
@client = ChannelManagerTestClient.new("localhost", @port, {
:nick => "foonick",
:user => "foouser",
:real => "foo real name",
:pass => "foopass",
:logger => Logger.new(nil),
})
@client.start
end
Thread.pass
true until @client
end
server_q = ChannelManagerTestServerSession.testq
client_q = ChannelManagerTestClient.testq
it "client should manage channel mode/users correctly" do
client = @client
client.instance_variable_set(:@prefix, Prefix.new("foonick!foouser@localhost"))
true until ChannelManagerTestServerSession.instance
ChannelManagerTestServerSession.instance.instance_eval do
Thread.exclusive do
post client.prefix, JOIN, "#test"
post nil, NOTICE, "#test", "sep1"
end
end
true until client_q.pop.to_s == "NOTICE #test sep1\r\n"
c = @client.instance_variable_get(:@channels)
c.synchronize do
c.should be_a_kind_of(Hash)
c["#test"].should be_a_kind_of(Hash)
c["#test"][:modes].should be_a_kind_of(Array)
c["#test"][:users].should be_a_kind_of(Array)
c["#test"][:users].should == ["foonick"]
end
ChannelManagerTestServerSession.instance.instance_eval do
Thread.exclusive do
post "test1!test@localhost", JOIN, "#test"
post "test2!test@localhost", JOIN, "#test"
post nil, NOTICE, "#test", "sep2"
end
end
true until client_q.pop.to_s == "NOTICE #test sep2\r\n"
c.synchronize do
c["#test"][:users].should == ["foonick", "test1", "test2"]
end
ChannelManagerTestServerSession.instance.instance_eval do
Thread.exclusive do
post nil, RPL_NAMREPLY, client.prefix.nick, "@", "#test", "foo1 foo2 foo3 @foo4 +foo5"
post nil, NOTICE, "#test", "sep3"
end
end
true until client_q.pop.to_s == "NOTICE #test sep3\r\n"
c.synchronize do
c["#test"][:users].should == ["foonick", "test1", "test2", "foo1", "foo2", "foo3", "foo4", "foo5"]
c["#test"][:modes].should include([:s, nil])
c["#test"][:modes].should include([:o, "foo4"])
c["#test"][:modes].should include([:v, "foo5"])
end
ChannelManagerTestServerSession.instance.instance_eval do
Thread.exclusive do
post nil, RPL_NAMREPLY, client.prefix.nick, "@", "#test1", "foo1 foo2 foo3 @foo4 +foo5"
post "foo4!foo@localhost", QUIT, "message"
post "foo5!foo@localhost", PART, "#test1", "message"
post client.prefix, KICK, "#test", "foo1", "message"
post client.prefix, MODE, "#test", "+o", "foo2"
post nil, NOTICE, "#test", "sep4"
end
end
true until client_q.pop.to_s == "NOTICE #test sep4\r\n"
c.synchronize do
c["#test"][:users].should == ["foonick", "test1", "test2", "foo2", "foo3", "foo5"]
c["#test1"][:users].should == ["foo1", "foo2", "foo3"]
c["#test"][:modes].should_not include([:o, "foo4"])
c["#test"][:modes].should include([:v, "foo5"])
c["#test1"][:modes].should_not include([:v, "foo5"])
c["#test"][:modes].should include([:o, "foo2"])
end
ChannelManagerTestServerSession.instance.instance_eval do
Thread.exclusive do
post "foonick!test@localhost", NICK, "foonick2"
post "foonick2!test@localhost", NICK, "foonick"
post "foo2!test@localhost", NICK, "bar2"
post "foo3!test@localhost", NICK, "bar3"
post nil, NOTICE, "#test", "sep5"
end
end
true until client_q.pop.to_s == "NOTICE #test sep5\r\n"
c.synchronize do
c["#test"][:users].should == ["foonick", "test1", "test2", "bar2", "bar3", "foo5"]
c["#test1"][:users].should == ["foo1", "bar2", "bar3"]
c["#test"][:modes].should_not include([:o, "foo4"])
c["#test"][:modes].should include([:v, "foo5"])
c["#test1"][:modes].should_not include([:v, "foo5"])
c["#test"][:modes].should_not include([:o, "foo2"])
c["#test"][:modes].should include([:o, "bar2"])
end
end
after :all do
@server.finish
@client.finish
end
end
net-irc-0.0.9/metadata.yml 0000664 0001750 0001750 00000003517 11604616526 015237 0 ustar uwabami uwabami --- !ruby/object:Gem::Specification
name: net-irc
version: !ruby/object:Gem::Version
version: 0.0.9
platform: ruby
authors:
- cho45
autorequire: ""
bindir: bin
cert_chain: []
date: 2009-10-11 00:00:00 +09:00
default_executable:
dependencies: []
description: library for implementing IRC server and client
email: cho45@lowreal.net
executables: []
extensions: []
extra_rdoc_files:
- README
- ChangeLog
files:
- README
- ChangeLog
- Rakefile
- spec/channel_manager_spec.rb
- spec/modeparser_spec.rb
- spec/net-irc_spec.rb
- spec/spec.opts
- lib/net/irc/client/channel_manager.rb
- lib/net/irc/client.rb
- lib/net/irc/constants.rb
- lib/net/irc/message/modeparser.rb
- lib/net/irc/message/serverconfig.rb
- lib/net/irc/message.rb
- lib/net/irc/pattern.rb
- lib/net/irc/server.rb
- lib/net/irc.rb
- examples/2ch.rb
- examples/2ig.rb
- examples/client.rb
- examples/echo_bot.rb
- examples/gmail.rb
- examples/hatena-star-stream.rb
- examples/hig.rb
- examples/iig.rb
- examples/ircd.rb
- examples/lig.rb
- examples/lingr.rb
- examples/mixi.rb
- examples/sig.rb
- examples/tig.rb
- examples/wig.rb
has_rdoc: true
homepage: http://cho45.stfuawsc.com/net-irc/
licenses: []
post_install_message:
rdoc_options:
- --title
- net-irc documentation
- --charset
- utf-8
- --opname
- index.html
- --line-numbers
- --main
- README
- --inline-source
- --exclude
- ^(examples|extras)/
require_paths:
- lib
required_ruby_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
required_rubygems_version: !ruby/object:Gem::Requirement
requirements:
- - ">="
- !ruby/object:Gem::Version
version: "0"
version:
requirements: []
rubyforge_project:
rubygems_version: 1.3.5
signing_key:
specification_version: 3
summary: library for implementing IRC server and client
test_files: []
net-irc-0.0.9/lib/ 0000775 0001750 0001750 00000000000 11604616526 013474 5 ustar uwabami uwabami net-irc-0.0.9/lib/net/ 0000775 0001750 0001750 00000000000 11604616526 014262 5 ustar uwabami uwabami net-irc-0.0.9/lib/net/irc/ 0000775 0001750 0001750 00000000000 11604616526 015037 5 ustar uwabami uwabami net-irc-0.0.9/lib/net/irc/pattern.rb 0000644 0001750 0001750 00000005374 11604616526 017050 0 ustar uwabami uwabami # coding: ASCII-8BIT
module Net::IRC::PATTERN # :nodoc:
# letter = %x41-5A / %x61-7A ; A-Z / a-z
# digit = %x30-39 ; 0-9
# hexdigit = digit / "A" / "B" / "C" / "D" / "E" / "F"
# special = %x5B-60 / %x7B-7D
# ; "[", "]", "\", "`", "_", "^", "{", "|", "}"
LETTER = 'A-Za-z'
DIGIT = '0-9'
HEXDIGIT = "#{DIGIT}A-Fa-f"
SPECIAL = '\x5B-\x60\x7B-\x7D'
# shortname = ( letter / digit ) *( letter / digit / "-" )
# *( letter / digit )
# ; as specified in RFC 1123 [HNAME]
# hostname = shortname *( "." shortname )
SHORTNAME = "[#{LETTER}#{DIGIT}](?:[-#{LETTER}#{DIGIT}]*[#{LETTER}#{DIGIT}])?"
HOSTNAME = "#{SHORTNAME}(?:\\.#{SHORTNAME})*"
# servername = hostname
SERVERNAME = HOSTNAME
# nickname = ( letter / special ) *8( letter / digit / special / "-" )
#NICKNAME = "[#{LETTER}#{SPECIAL}#{DIGIT}_][-#{LETTER}#{DIGIT}#{SPECIAL}]*"
NICKNAME = "\\S+" # for multibytes
# user = 1*( %x01-09 / %x0B-0C / %x0E-1F / %x21-3F / %x41-FF )
# ; any octet except NUL, CR, LF, " " and "@"
USER = '[\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x3F\x41-\xFF]+'
# ip4addr = 1*3digit "." 1*3digit "." 1*3digit "." 1*3digit
IP4ADDR = "[#{DIGIT}]{1,3}(?:\\.[#{DIGIT}]{1,3}){3}"
# ip6addr = 1*hexdigit 7( ":" 1*hexdigit )
# ip6addr =/ "0:0:0:0:0:" ( "0" / "FFFF" ) ":" ip4addr
IP6ADDR = "(?:[#{HEXDIGIT}]+(?::[#{HEXDIGIT}]+){7}|0:0:0:0:0:(?:0|FFFF):#{IP4ADDR})"
# hostaddr = ip4addr / ip6addr
HOSTADDR = "(?:#{IP4ADDR}|#{IP6ADDR})"
# host = hostname / hostaddr
HOST = "(?:#{HOSTNAME}|#{HOSTADDR})"
# prefix = servername / ( nickname [ [ "!" user ] "@" host ] )
PREFIX = "(?:#{NICKNAME}(?:(?:!#{USER})?@#{HOST})?|#{SERVERNAME})"
# nospcrlfcl = %x01-09 / %x0B-0C / %x0E-1F / %x21-39 / %x3B-FF
# ; any octet except NUL, CR, LF, " " and ":"
NOSPCRLFCL = '\x01-\x09\x0B-\x0C\x0E-\x1F\x21-\x39\x3B-\xFF'
# command = 1*letter / 3digit
COMMAND = "(?:[#{LETTER}]+|[#{DIGIT}]{3})"
# SPACE = %x20 ; space character
# middle = nospcrlfcl *( ":" / nospcrlfcl )
# trailing = *( ":" / " " / nospcrlfcl )
# params = *14( SPACE middle ) [ SPACE ":" trailing ]
# =/ 14( SPACE middle ) [ SPACE [ ":" ] trailing ]
MIDDLE = "[#{NOSPCRLFCL}][:#{NOSPCRLFCL}]*"
TRAILING = "[: #{NOSPCRLFCL}]*"
PARAMS = "(?:((?: #{MIDDLE}){0,14})(?: :(#{TRAILING}))?|((?: #{MIDDLE}){14}):?(#{TRAILING}))"
# crlf = %x0D %x0A ; "carriage return" "linefeed"
# message = [ ":" prefix SPACE ] command [ params ] crlf
CRLF = '\x0D\x0A'
MESSAGE = "(?::(#{PREFIX}) )?(#{COMMAND})#{PARAMS}\s*#{CRLF}"
CLIENT_PATTERN = /\A#{NICKNAME}(?:(?:!#{USER})?@#{HOST})\z/on
MESSAGE_PATTERN = /\A#{MESSAGE}\z/on
end # PATTERN
net-irc-0.0.9/lib/net/irc/client.rb 0000644 0001750 0001750 00000004647 11604616526 016653 0 ustar uwabami uwabami class Net::IRC::Client
include Net::IRC
include Constants
attr_reader :host, :port, :opts
attr_reader :prefix, :channels
def initialize(host, port, opts={})
@host = host
@port = port
@opts = OpenStruct.new(opts)
@log = @opts.logger || Logger.new($stdout)
@server_config = Message::ServerConfig.new
@channels = {
# "#channel" => {
# :modes => [],
# :users => [],
# }
}
@channels.extend(MonitorMixin)
end
# Connect to server and start loop.
def start
# reset config
@server_config = Message::ServerConfig.new
@socket = TCPSocket.open(@host, @port)
on_connected
post PASS, @opts.pass if @opts.pass
post NICK, @opts.nick
post USER, @opts.user, "0", "*", @opts.real
while l = @socket.gets
begin
@log.debug "RECEIVE: #{l.chomp}"
m = Message.parse(l)
next if on_message(m) === true
name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
send(name, m) if respond_to?(name)
rescue Exception => e
warn e
warn e.backtrace.join("\r\t")
raise
rescue Message::InvalidMessage
@log.error "MessageParse: " + l.inspect
end
end
rescue IOError
ensure
finish
end
# Close connection to server.
def finish
begin
@socket.close
rescue
end
on_disconnected
end
# Catch all messages.
# If this method return true, aother callback will not be called.
def on_message(m)
end
# Default RPL_WELCOME callback.
# This sets @prefix from the message.
def on_rpl_welcome(m)
@prefix = Prefix.new(m[1][/\S+\z/])
end
# Default RPL_ISUPPORT callback.
# This detects server's configurations.
def on_rpl_isupport(m)
@server_config.set(m)
end
# Default PING callback. Response PONG.
def on_ping(m)
post PONG, @prefix ? @prefix.nick : ""
end
# Do nothing.
# This is for avoiding error on calling super.
# So you can always call super at subclass.
def method_missing(name, *args)
end
# Call when socket connected.
def on_connected
end
# Call when socket closed.
def on_disconnected
end
private
# Post message to server.
#
# include Net::IRC::Constants
# post PRIVMSG, "#channel", "foobar"
def post(command, *params)
m = Message.new(nil, command, params.map {|s|
if s
s.force_encoding("ASCII-8BIT") if s.respond_to? :force_encoding
#s.gsub(/\r\n|[\r\n]/, " ")
s.tr("\r\n", " ")
else
""
end
})
@log.debug "SEND: #{m.to_s.chomp}"
@socket << m
end
end # Client
net-irc-0.0.9/lib/net/irc/message.rb 0000644 0001750 0001750 00000004106 11604616526 017007 0 ustar uwabami uwabami class Net::IRC::Message
include Net::IRC
class InvalidMessage < Net::IRC::IRCException; end
attr_reader :prefix, :command, :params
# Parse string and return new Message.
# If the string is invalid message, this method raises Net::IRC::Message::InvalidMessage.
def self.parse(str)
_, prefix, command, *rest = *PATTERN::MESSAGE_PATTERN.match(str)
raise InvalidMessage, "Invalid message: #{str.dump}" unless _
case
when rest[0] && !rest[0].empty?
middle, trailer, = *rest
when rest[2] && !rest[2].empty?
middle, trailer, = *rest[2, 2]
when rest[1]
params = []
trailer = rest[1]
when rest[3]
params = []
trailer = rest[3]
else
params = []
end
params ||= middle.split(/ /)[1..-1]
params << trailer if trailer
new(prefix, command, params)
end
def initialize(prefix, command, params)
@prefix = Prefix.new(prefix.to_s)
@command = command
@params = params
end
# Same as @params[n].
def [](n)
@params[n]
end
# Iterate params.
def each(&block)
@params.each(&block)
end
# Stringfy message to raw IRC message.
def to_s
str = ""
str << ":#{@prefix} " unless @prefix.empty?
str << @command
if @params
f = false
@params.each do |param|
f = !f && (param.empty? || param[0] == ?: || param.include?(" "))
str << " "
str << ":" if f
str << param
end
end
str << "\x0D\x0A"
str
end
alias to_str to_s
# Same as params.
def to_a
@params.dup
end
# If the message is CTCP, return true.
def ctcp?
#message = @params[1]
#message[0] == ?\01 && message[-1] == ?\01
/\x01(?>[^\x00\x01\r\n]*)\x01/ === @params[1]
end
def ctcps
messages = []
@params[1].gsub!(/\x01(?>[^\x00\x01\r\n]*)\x01/) do
messages << ctcp_decode($&)
""
end
messages
end
def inspect
"#<%s:0x%x prefix:%s command:%s params:%s>" % [
self.class,
self.object_id,
@prefix,
@command,
@params.inspect
]
end
autoload :ModeParser, "net/irc/message/modeparser"
autoload :ServerConfig, "net/irc/message/serverconfig"
#autoload :ISupportModeParser, "net/irc/message/isupportmodeparser"
end # Message
net-irc-0.0.9/lib/net/irc/constants.rb 0000644 0001750 0001750 00000015017 11604616526 017402 0 ustar uwabami uwabami module Net::IRC::Constants # :nodoc:
RPL_WELCOME = '001'
RPL_YOURHOST = '002'
RPL_CREATED = '003'
RPL_MYINFO = '004'
RPL_ISUPPORT = '005'
RPL_USERHOST = '302'
RPL_ISON = '303'
RPL_AWAY = '301'
RPL_UNAWAY = '305'
RPL_NOWAWAY = '306'
RPL_WHOISUSER = '311'
RPL_WHOISSERVER = '312'
RPL_WHOISOPERATOR = '313'
RPL_WHOISIDLE = '317'
RPL_ENDOFWHOIS = '318'
RPL_WHOISCHANNELS = '319'
RPL_WHOWASUSER = '314'
RPL_ENDOFWHOWAS = '369'
RPL_LISTSTART = '321'
RPL_LIST = '322'
RPL_LISTEND = '323'
RPL_UNIQOPIS = '325'
RPL_CHANNELMODEIS = '324'
RPL_NOTOPIC = '331'
RPL_TOPIC = '332'
RPL_INVITING = '341'
RPL_SUMMONING = '342'
RPL_INVITELIST = '346'
RPL_ENDOFINVITELIST = '347'
RPL_EXCEPTLIST = '348'
RPL_ENDOFEXCEPTLIST = '349'
RPL_VERSION = '351'
RPL_WHOREPLY = '352'
RPL_ENDOFWHO = '315'
RPL_NAMREPLY = '353'
RPL_ENDOFNAMES = '366'
RPL_LINKS = '364'
RPL_ENDOFLINKS = '365'
RPL_BANLIST = '367'
RPL_ENDOFBANLIST = '368'
RPL_INFO = '371'
RPL_ENDOFINFO = '374'
RPL_MOTDSTART = '375'
RPL_MOTD = '372'
RPL_ENDOFMOTD = '376'
RPL_YOUREOPER = '381'
RPL_REHASHING = '382'
RPL_YOURESERVICE = '383'
RPL_TIME = '391'
RPL_USERSSTART = '392'
RPL_USERS = '393'
RPL_ENDOFUSERS = '394'
RPL_NOUSERS = '395'
RPL_TRACELINK = '200'
RPL_TRACECONNECTING = '201'
RPL_TRACEHANDSHAKE = '202'
RPL_TRACEUNKNOWN = '203'
RPL_TRACEOPERATOR = '204'
RPL_TRACEUSER = '205'
RPL_TRACESERVER = '206'
RPL_TRACESERVICE = '207'
RPL_TRACENEWTYPE = '208'
RPL_TRACECLASS = '209'
RPL_TRACERECONNECT = '210'
RPL_TRACELOG = '261'
RPL_TRACEEND = '262'
RPL_STATSLINKINFO = '211'
RPL_STATSCOMMANDS = '212'
RPL_ENDOFSTATS = '219'
RPL_STATSUPTIME = '242'
RPL_STATSOLINE = '243'
RPL_UMODEIS = '221'
RPL_SERVLIST = '234'
RPL_SERVLISTEND = '235'
RPL_LUSERCLIENT = '251'
RPL_LUSEROP = '252'
RPL_LUSERUNKNOWN = '253'
RPL_LUSERCHANNELS = '254'
RPL_LUSERME = '255'
RPL_ADMINME = '256'
RPL_ADMINLOC1 = '257'
RPL_ADMINLOC2 = '258'
RPL_ADMINEMAIL = '259'
RPL_TRYAGAIN = '263'
ERR_NOSUCHNICK = '401'
ERR_NOSUCHSERVER = '402'
ERR_NOSUCHCHANNEL = '403'
ERR_CANNOTSENDTOCHAN = '404'
ERR_TOOMANYCHANNELS = '405'
ERR_WASNOSUCHNICK = '406'
ERR_TOOMANYTARGETS = '407'
ERR_NOSUCHSERVICE = '408'
ERR_NOORIGIN = '409'
ERR_NORECIPIENT = '411'
ERR_NOTEXTTOSEND = '412'
ERR_NOTOPLEVEL = '413'
ERR_WILDTOPLEVEL = '414'
ERR_BADMASK = '415'
ERR_UNKNOWNCOMMAND = '421'
ERR_NOMOTD = '422'
ERR_NOADMININFO = '423'
ERR_FILEERROR = '424'
ERR_NONICKNAMEGIVEN = '431'
ERR_ERRONEUSNICKNAME = '432'
ERR_NICKNAMEINUSE = '433'
ERR_NICKCOLLISION = '436'
ERR_UNAVAILRESOURCE = '437'
ERR_USERNOTINCHANNEL = '441'
ERR_NOTONCHANNEL = '442'
ERR_USERONCHANNEL = '443'
ERR_NOLOGIN = '444'
ERR_SUMMONDISABLED = '445'
ERR_USERSDISABLED = '446'
ERR_NOTREGISTERED = '451'
ERR_NEEDMOREPARAMS = '461'
ERR_ALREADYREGISTRED = '462'
ERR_NOPERMFORHOST = '463'
ERR_PASSWDMISMATCH = '464'
ERR_YOUREBANNEDCREEP = '465'
ERR_YOUWILLBEBANNED = '466'
ERR_KEYSET = '467'
ERR_CHANNELISFULL = '471'
ERR_UNKNOWNMODE = '472'
ERR_INVITEONLYCHAN = '473'
ERR_BANNEDFROMCHAN = '474'
ERR_BADCHANNELKEY = '475'
ERR_BADCHANMASK = '476'
ERR_NOCHANMODES = '477'
ERR_BANLISTFULL = '478'
ERR_NOPRIVILEGES = '481'
ERR_CHANOPRIVSNEEDED = '482'
ERR_CANTKILLSERVER = '483'
ERR_RESTRICTED = '484'
ERR_UNIQOPPRIVSNEEDED = '485'
ERR_NOOPERHOST = '491'
ERR_UMODEUNKNOWNFLAG = '501'
ERR_USERSDONTMATCH = '502'
RPL_SERVICEINFO = '231'
RPL_ENDOFSERVICES = '232'
RPL_SERVICE = '233'
RPL_NONE = '300'
RPL_WHOISCHANOP = '316'
RPL_KILLDONE = '361'
RPL_CLOSING = '362'
RPL_CLOSEEND = '363'
RPL_INFOSTART = '373'
RPL_MYPORTIS = '384'
RPL_STATSCLINE = '213'
RPL_STATSNLINE = '214'
RPL_STATSILINE = '215'
RPL_STATSKLINE = '216'
RPL_STATSQLINE = '217'
RPL_STATSYLINE = '218'
RPL_STATSVLINE = '240'
RPL_STATSLLINE = '241'
RPL_STATSHLINE = '244'
RPL_STATSSLINE = '244'
RPL_STATSPING = '246'
RPL_STATSBLINE = '247'
RPL_STATSDLINE = '250'
ERR_NOSERVICEHOST = '492'
PASS = 'PASS'
NICK = 'NICK'
USER = 'USER'
OPER = 'OPER'
MODE = 'MODE'
SERVICE = 'SERVICE'
QUIT = 'QUIT'
SQUIT = 'SQUIT'
JOIN = 'JOIN'
PART = 'PART'
TOPIC = 'TOPIC'
NAMES = 'NAMES'
LIST = 'LIST'
INVITE = 'INVITE'
KICK = 'KICK'
PRIVMSG = 'PRIVMSG'
NOTICE = 'NOTICE'
MOTD = 'MOTD'
LUSERS = 'LUSERS'
VERSION = 'VERSION'
STATS = 'STATS'
LINKS = 'LINKS'
TIME = 'TIME'
CONNECT = 'CONNECT'
TRACE = 'TRACE'
ADMIN = 'ADMIN'
INFO = 'INFO'
SERVLIST = 'SERVLIST'
SQUERY = 'SQUERY'
WHO = 'WHO'
WHOIS = 'WHOIS'
WHOWAS = 'WHOWAS'
KILL = 'KILL'
PING = 'PING'
PONG = 'PONG'
ERROR = 'ERROR'
AWAY = 'AWAY'
REHASH = 'REHASH'
DIE = 'DIE'
RESTART = 'RESTART'
SUMMON = 'SUMMON'
USERS = 'USERS'
WALLOPS = 'WALLOPS'
USERHOST = 'USERHOST'
ISON = 'ISON'
end
Net::IRC::COMMANDS = Net::IRC::Constants.constants.inject({}) {|r, i| # :nodoc:
r.update(Net::IRC::Constants.const_get(i).to_s => i.to_s.freeze)
}
net-irc-0.0.9/lib/net/irc/server.rb 0000644 0001750 0001750 00000010047 11604616526 016672 0 ustar uwabami uwabami class Net::IRC::Server
# Server global state for accessing Server::Session
attr_accessor :state
attr_accessor :sessions
def initialize(host, port, session_class, opts={})
@host = host
@port = port
@session_class = session_class
@opts = OpenStruct.new(opts)
@sessions = []
@state = {}
end
# Start server loop.
def start
@serv = TCPServer.new(@host, @port)
@log = @opts.logger || Logger.new($stdout)
@log.info "Host: #{@host} Port:#{@port}"
@accept = Thread.start do
loop do
Thread.start(@serv.accept) do |s|
begin
@log.info "Client connected, new session starting..."
s = @session_class.new(self, s, @log, @opts)
@sessions << s
s.start
rescue Exception => e
puts e
puts e.backtrace
ensure
@sessions.delete(s)
end
end
end
end
@accept.join
end
# Close all sessions.
def finish
Thread.exclusive do
@accept.kill
begin
@serv.close
rescue
end
@sessions.each do |s|
s.finish
end
end
end
class Session
include Net::IRC
include Constants
attr_reader :prefix, :nick, :real, :host
# Override subclass.
def server_name
"net-irc"
end
# Override subclass.
def server_version
"0.0.0"
end
# Override subclass.
def available_user_modes
"eixwy"
end
# Override subclass.
def available_channel_modes
"spknm"
end
def initialize(server, socket, logger, opts={})
@server, @socket, @log, @opts = server, socket, logger, opts
end
def self.start(*args)
new(*args).start
end
# Start session loop.
def start
on_connected
while l = @socket.gets
begin
@log.debug "RECEIVE: #{l.chomp}"
m = Message.parse(l)
next if on_message(m) === true
name = "on_#{(COMMANDS[m.command.upcase] || m.command).downcase}"
send(name, m) if respond_to?(name)
break if m.command == QUIT
rescue Message::InvalidMessage
@log.error "MessageParse: " + l.inspect
end
end
rescue IOError
ensure
finish
end
# Close this session.
def finish
begin
@socket.close
rescue
end
on_disconnected
end
# Default PASS callback.
# Set @pass.
def on_pass(m)
@pass = m.params[0]
end
# Default NICK callback.
# Set @nick.
def on_nick(m)
@nick = m.params[0]
@prefix = Prefix.new("#{@nick}!#{@user}@#{@host}") if defined? @prefix
end
# Default USER callback.
# Set @user, @real, @host and call initial_message.
def on_user(m)
@user, @real = m.params[0], m.params[3]
@nick ||= @user
@host = @socket.peeraddr[2]
@prefix = Prefix.new("#{@nick}!#{@user}@#{@host}")
initial_message
end
# Call when socket connected.
def on_connected
end
# Call when socket closed.
def on_disconnected
end
# Catch all messages.
# If this method return true, aother callback will not be called.
def on_message(m)
end
# Default PING callback. Response PONG.
def on_ping(m)
post server_name, PONG, m.params[0]
end
# Do nothing.
# This is for avoiding error on calling super.
# So you can always call super at subclass.
def method_missing(name, *args)
end
private
# Post message to server.
#
# include Net::IRC::Constants
# post prefix, PRIVMSG, "#channel", "foobar"
def post(prefix, command, *params)
m = Message.new(prefix, command, params.map {|s|
#s.gsub(/\r\n|[\r\n]/, " ")
s.tr("\r\n", " ")
})
@log.debug "SEND: #{m.to_s.chomp}"
@socket << m
rescue IOError
finish
end
# Call when client connected.
# Send RPL_WELCOME sequence. If you want to customize, override this method at subclass.
def initial_message
post server_name, RPL_WELCOME, @nick, "Welcome to the Internet Relay Network #{@prefix}"
post server_name, RPL_YOURHOST, @nick, "Your host is #{server_name}, running version #{server_version}"
post server_name, RPL_CREATED, @nick, "This server was created #{Time.now}"
post server_name, RPL_MYINFO, @nick, "#{server_name} #{server_version} #{available_user_modes} #{available_channel_modes}"
end
end
end # Server
net-irc-0.0.9/lib/net/irc/message/ 0000775 0001750 0001750 00000000000 11604616526 016463 5 ustar uwabami uwabami net-irc-0.0.9/lib/net/irc/message/modeparser.rb 0000644 0001750 0001750 00000003265 11604616526 021155 0 ustar uwabami uwabami class Net::IRC::Message::ModeParser
ONE_PARAM_MASK = 0
ONE_PARAM = 1
ONE_PARAM_FOR_POSITIVE = 2
NO_PARAM = 3
def initialize
@modes = {}
@op_to_mark_map = {}
@mark_to_op_map = {}
# Initialize for ircd 2.11 (RFC2812+)
set(:CHANMODES, 'beIR,k,l,imnpstaqr')
set(:PREFIX, '(ov)@+')
end
def mark_to_op(mark)
mark.empty? ? nil : @mark_to_op_map[mark.to_sym]
end
def set(key, value)
case key
when :PREFIX
if value =~ /\A\(([a-zA-Z]+)\)(.+)\z/
@op_to_mark_map = {}
key, value = Regexp.last_match[1], Regexp.last_match[2]
key.scan(/./).zip(value.scan(/./)) {|pair|
@op_to_mark_map[pair[0].to_sym] = pair[1].to_sym
}
@mark_to_op_map = @op_to_mark_map.invert
end
when :CHANMODES
@modes = {}
value.split(",").each_with_index do |s, kind|
s.scan(/./).each {|c|
@modes[c.to_sym] = kind
}
end
end
end
def parse(arg)
params = arg.kind_of?(Net::IRC::Message) ? arg.to_a : arg.split(" ")
params.shift
ret = {
:positive => [],
:negative => [],
}
current = ret[:positive]
until params.empty?
s = params.shift
s.scan(/./).each do |c|
c = c.to_sym
case c
when :+
current = ret[:positive]
when :-
current = ret[:negative]
else
case @modes[c]
when ONE_PARAM_MASK,ONE_PARAM
current << [c, params.shift]
when ONE_PARAM_FOR_POSITIVE
if current.equal?(ret[:positive])
current << [c, params.shift]
else
current << [c, nil]
end
when NO_PARAM
current << [c, nil]
else
if @op_to_mark_map[c]
current << [c, params.shift]
end
end
end
end
end
ret
end
end
net-irc-0.0.9/lib/net/irc/message/serverconfig.rb 0000644 0001750 0001750 00000001150 11604616526 021477 0 ustar uwabami uwabami class Net::IRC::Message::ServerConfig
attr_reader :mode_parser
def initialize
@config = {}
@mode_parser = Net::IRC::Message::ModeParser.new
end
def set(arg)
params = arg.kind_of?(Net::IRC::Message) ? arg.to_a : arg.split(" ")
params[1..-1].each do |s|
case s
when /\A:?are supported by this server\z/
# Ignore
when /\A([^=]+)=(.*)\z/
key = Regexp.last_match[1].to_sym
value = Regexp.last_match[2]
@config[key] = value
@mode_parser.set(key, value) if key == :CHANMODES || key == :PREFIX
else
@config[s] = true
end
end
end
def [](key)
@config[key]
end
end
net-irc-0.0.9/lib/net/irc/client/ 0000775 0001750 0001750 00000000000 11604616526 016315 5 ustar uwabami uwabami net-irc-0.0.9/lib/net/irc/client/channel_manager.rb 0000644 0001750 0001750 00000005115 11604616526 021744 0 ustar uwabami uwabami
module Net::IRC::Client::ChannelManager
# For managing channel
def on_rpl_namreply(m)
type = m[1]
channel = m[2]
init_channel(channel)
@channels.synchronize do
m[3].split(" ").each do |u|
_, mode, nick = *u.match(/\A([@+]?)(.+)/)
@channels[channel][:users] << nick
@channels[channel][:users].uniq!
op = @server_config.mode_parser.mark_to_op(mode)
if op
@channels[channel][:modes] << [op, nick]
end
end
case type
when "@" # secret
@channels[channel][:modes] << [:s, nil]
when "*" # private
@channels[channel][:modes] << [:p, nil]
when "=" # public
end
@channels[channel][:modes].uniq!
end
end
# For managing channel
def on_part(m)
nick = m.prefix.nick
channel = m[0]
init_channel(channel)
@channels.synchronize do
info = @channels[channel]
if info
info[:users].delete(nick)
info[:modes].delete_if {|u|
u[1] == nick
}
end
end
end
# For managing channel
def on_quit(m)
nick = m.prefix.nick
@channels.synchronize do
@channels.each do |channel, info|
info[:users].delete(nick)
info[:modes].delete_if {|u|
u[1] == nick
}
end
end
end
# For managing channel
def on_kick(m)
users = m[1].split(",")
@channels.synchronize do
m[0].split(",").each do |chan|
init_channel(chan)
info = @channels[chan]
if info
users.each do |nick|
info[:users].delete(nick)
info[:modes].delete_if {|u|
u[1] == nick
}
end
end
end
end
end
# For managing channel
def on_join(m)
nick = m.prefix.nick
channel = m[0]
@channels.synchronize do
init_channel(channel)
@channels[channel][:users] << nick
@channels[channel][:users].uniq!
end
end
# For managing channel
def on_nick(m)
oldnick = m.prefix.nick
newnick = m[0]
@channels.synchronize do
@channels.each do |channel, info|
info[:users].map! {|i|
(i == oldnick) ? newnick : i
}
info[:modes].map! {|mode, target|
(target == oldnick) ? [mode, newnick] : [mode, target]
}
end
end
end
# For managing channel
def on_mode(m)
channel = m[0]
@channels.synchronize do
init_channel(channel)
modes = @server_config.mode_parser.parse(m)
modes[:negative].each do |mode|
@channels[channel][:modes].delete(mode)
end
modes[:positive].each do |mode|
@channels[channel][:modes] << mode
end
@channels[channel][:modes].uniq!
[modes[:negative], modes[:positive]]
end
end
# For managing channel
def init_channel(channel)
@channels[channel] ||= {
:modes => [],
:users => [],
}
end
end
net-irc-0.0.9/lib/net/irc.rb 0000644 0001750 0001750 00000002746 11604616526 015373 0 ustar uwabami uwabami #!ruby
require "ostruct"
require "socket"
require "logger"
require "monitor"
module Net; end
module Net::IRC
VERSION = "0.0.9".freeze
class IRCException < StandardError; end
require "net/irc/constants"
require "net/irc/pattern"
autoload :Message, "net/irc/message"
autoload :Client, "net/irc/client"
autoload :Server, "net/irc/server"
class Prefix < String
def nick
extract[0]
end
def user
extract[1]
end
def host
extract[2]
end
# Extract Prefix String to [nick, user, host] Array.
def extract
_, *ret = *self.match(/\A([^\s!]+)(?:!([^\s@]+)@(\S+))?\z/)
ret
end
end
# Encode to CTCP message. Prefix and postfix \x01.
def ctcp_encode(str)
"\x01#{ctcp_quote(str)}\x01"
end
#alias :ctcp_encoding :ctcp_encode
module_function :ctcp_encode #, :ctcp_encoding
# Decode from CTCP message delimited with \x01.
def ctcp_decode(str)
ctcp_dequote(str.delete("\x01"))
end
#alias :ctcp_decoding :ctcp_decode
module_function :ctcp_decode #, :ctcp_decoding
def ctcp_quote(str)
low_quote(str.gsub("\\", "\\\\\\\\").gsub("\x01", "\\a"))
end
module_function :ctcp_quote
def ctcp_dequote(str)
low_dequote(str).gsub("\\a", "\x01").gsub(/\\(.|\z)/m, "\\1")
end
module_function :ctcp_dequote
private
def low_quote(str)
str.gsub("\x10", "\x10\x10").gsub("\x00", "\x10\x30").gsub("\r", "\x10r").gsub("\n", "\x10n")
end
def low_dequote(str)
str.gsub("\x10n", "\n").gsub("\x10r", "\r").gsub("\x10\x30", "\x00").gsub("\x10\x10", "\x10")
end
end
net-irc-0.0.9/Rakefile 0000644 0001750 0001750 00000005721 11604616526 014376 0 ustar uwabami uwabami require 'rubygems'
require "shipit"
require 'rake'
require 'rake/clean'
require 'rake/packagetask'
require 'rake/gempackagetask'
require 'rake/rdoctask'
require 'rake/contrib/sshpublisher'
require 'fileutils'
require 'spec/rake/spectask'
include FileUtils
$LOAD_PATH.unshift "lib"
require "net/irc"
NAME = "net-irc"
AUTHOR = "cho45"
EMAIL = "cho45@lowreal.net"
DESCRIPTION = "library for implementing IRC server and client"
HOMEPATH = "http://cho45.stfuawsc.com/net-irc/"
BIN_FILES = %w( )
VERS = Net::IRC::VERSION.dup
REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
RDOC_OPTS = [
'--title', "#{NAME} documentation",
"--charset", "utf-8",
"--opname", "index.html",
"--line-numbers",
"--main", "README",
"--inline-source",
]
task :default => [:spec]
task :package => [:clean]
Spec::Rake::SpecTask.new do |t|
t.spec_opts = ['--options', "spec/spec.opts"]
t.spec_files = FileList['spec/*_spec.rb']
#t.rcov = true
end
spec = Gem::Specification.new do |s|
s.name = NAME
s.version = VERS
s.platform = Gem::Platform::RUBY
s.has_rdoc = true
s.extra_rdoc_files = ["README", "ChangeLog"]
s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
s.summary = DESCRIPTION
s.description = DESCRIPTION
s.author = AUTHOR
s.email = EMAIL
s.homepage = HOMEPATH
s.executables = BIN_FILES
s.bindir = "bin"
s.require_path = "lib"
s.autorequire = ""
#s.add_dependency('activesupport', '>=1.3.1')
#s.required_ruby_version = '>= 1.8.2'
s.files = %w(README ChangeLog Rakefile) +
Dir.glob("{bin,doc,spec,test,lib,templates,generator,extras,website,script}/**/*") +
Dir.glob("ext/**/*.{h,c,rb}") +
Dir.glob("examples/**/*.rb") +
Dir.glob("tools/*.rb")
s.extensions = FileList["ext/**/extconf.rb"].to_a
end
Rake::GemPackageTask.new(spec) do |p|
p.need_tar = true
p.gem_spec = spec
end
task :install do
name = "#{NAME}-#{VERS}.gem"
sh %{rake package}
sh %{sudo gem install pkg/#{name}}
end
task :uninstall => [:clean] do
sh %{sudo gem uninstall #{NAME}}
end
task :upload_doc => [:rdoc] do
sh %{rsync --update -avptr html/ lowreal@cho45.stfuawsc.com:/virtual/lowreal/public_html/cho45.stfuawsc.com/net-irc}
end
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = 'html'
rdoc.options += RDOC_OPTS
rdoc.template = "resh"
#rdoc.template = "#{ENV['template']}.rb" if ENV['template']
if ENV['DOC_FILES']
rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
else
rdoc.rdoc_files.include('README', 'ChangeLog')
rdoc.rdoc_files.include('lib/**/*.rb')
rdoc.rdoc_files.include('ext/**/*.c')
end
end
Rake::ShipitTask.new do |s|
s.ChangeVersion "lib/net/irc.rb", "VERSION"
s.Commit
s.Task :clean, :package, :upload_doc
s.Step.new {
}.and {
system("gem", "push", "pkg/net-irc-#{VERS}.gem")
}
s.Tag
s.Twitter
end