Skip to content
Browse files

Hatena::Counter IRC Gateway

  • Loading branch information...
1 parent dc9eb08 commit 3dc00eb3932a40e0d3f43f87fe7ddddb8284b2b2 @cho45 committed Jul 28, 2011
Showing with 252 additions and 0 deletions.
  1. +252 −0 examples/hcig.rb
View
252 examples/hcig.rb
@@ -0,0 +1,252 @@
+#!/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 'mechanize'
+require 'digest/sha1'
+
+Net::HTTP.version_1_2
+Thread.abort_on_exception = true
+
+class HatenaCounterIrcGateway < Net::IRC::Server::Session
+ def server_name
+ "hcig"
+ end
+
+ def server_version
+ "0.0.0"
+ end
+
+
+ def initialize(*args)
+ super
+ @channels = {}
+ @ua = Mechanize.new
+ 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+/)
+ self.rk = @real
+ @opts ||= []
+ end
+
+ def on_join(m)
+ channels = m.params.first.split(/,/)
+ channels.each do |channel|
+ @channels[channel] = {
+ :topic => "",
+ :time => Time.at(0),
+ :interval => 60,
+ :observer => nil,
+ } unless @channels.key?(channel)
+ create_observer(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 'rk'
+ self.rk = args[0]
+ end
+ rescue Exception => e
+ @log.error e.inspect
+ e.backtrace.each do |l|
+ @log.error "\t#{l}"
+ end
+ 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
+ pre, name, cid = *channel.split(/-/)
+
+ if !name || !cid
+ post @prefix, PART, channel, "You must join to #counter-[username]-[counter id]"
+ info[:observer].kill
+ next
+ end
+
+ loop do
+ begin
+ uri = "http://counter.hatena.ne.jp/#{name}/log?cid=#{cid}&date=&type="
+ @log.debug "Retriving... #{uri}"
+ ret = @ua.get(uri) do |page|
+ page.search('#log_table tr').reverse_each do |tr|
+ access = Access.new(*tr.search('td').map {|i| i.text.gsub("\302\240", ' ').gsub(/^\s+|\s+$/, '') })
+ next unless access.time
+ next if access.time < info[:time]
+ post access.ua_id, PRIVMSG, channel, "%s %s" % [access.request, access.host.gsub(/(\.\d+)+\./, '..').sub(/^[^.]+/, '')]
+ info[:time] = access.time
+ end
+ end
+ unless ret.code.to_i == 200
+ post nil, NOTICE, channel, "Server returned #{code}. Please refresh rk by /me rk [new rk]"
+ end
+ rescue Exception => e
+ @log.error "Error: #{e.inspect}"
+ e.backtrace.each do |l|
+ @log.error "\t#{l}"
+ end
+ end
+ @log.debug "#{channel}: sleep #{info[:interval]}"
+ sleep info[:interval]
+ end
+ end
+ end
+
+ def rk=(rk)
+ uri = URI.parse('http://www.hatena.ne.jp/')
+ @ua.cookie_jar.add(
+ uri,
+ Mechanize::Cookie.parse(uri, "rk=#{rk}; domain=.hatena.ne.jp").first
+ )
+ end
+
+ Access = Struct.new(:time_raw, :request, :ua, :lang, :screen, :host, :referrer) do
+ require 'time'
+
+ def time
+ time_raw ? Time.parse(time_raw) : nil
+ end
+
+ def digest
+ hostc = host.gsub(/\.[^.]+\.jp$|\.com$/, '')[/[^.]+$/]
+ @digest ||= Digest::SHA1.digest([ua, lang, screen, hostc].join("\n"))
+ end
+
+ def ua_id
+ @ua_id ||= [ digest ].pack('m')[0, 7]
+ end
+ end
+
+end
+
+if __FILE__ == $0
+ require "optparse"
+
+ opts = {
+ :port => 16801,
+ :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], HatenaCounterIrcGateway, opts).start
+ end
+end
+
+
+

0 comments on commit 3dc00eb

Please sign in to comment.
Something went wrong with that request. Please try again.