lib/atig/gateway/session.rb
# -*- mode:ruby; coding:utf-8 -*-
require 'fileutils'
require 'atig/util'
require "ostruct"
require "time"
require 'yaml'
require 'atig/url_escape'
require 'atig/twitter'
require 'atig/search_twitter'
require 'atig/stream'
require 'atig/oauth'
require 'atig/db/db'
require 'atig/gateway/channel'
require 'atig/option'
begin
require 'continuation'
rescue LoadError
end
module Atig
module Gateway
class Session < Net::IRC::Server::Session
include Util
class << self
def self.class_writer(*ids)
ids.each do|id|
module_eval <<END
def #{id}=(arg)
@@#{id} = arg
end
END
end
end
class_writer :commands, :agents, :ifilters, :ofilters, :channels
end
def initialize(*args)
super
@tmpdir = @opts.tmpdir
@on_message = nil
end
def post(*args)
super
end
def update_status(ret, target, msg='')
@db.transaction do|db|
db.statuses.add(source: :me, status: ret, user: ret.user )
end
msg = "(#{msg})" unless msg.empty?
self[target].notify "Status updated #{msg}"
end
def channel(name,opts={})
opts.update(session: self,
name: name,
filters: @ifilters,
prefix: @prefix,
nick: @nick,
opts: @opts)
channel = Channel.new opts
@channels[name] = channel
channel
end
def [](name)
@channels[name]
end
def output_message(query)
@ofilters.inject(query) {|x, f| f.call x }
end
def ctcp_action(*commands, &block)
commands.each do |command|
@ctcp_actions[command] = block
end
end
def prefix(u)
nick = u.screen_name
nick = "@#{nick}" if @opts.athack
user = "id=%.9d" % u.id
host = "twitter"
host += "/protected" if u.protected
Net::IRC::Prefix.new("#{nick}!#{user}@#{host}")
end
def topic(entry)
@channels.each{|_, ch| ch.topic entry }
end
def on_message(m)
GC.start
@on_message.call(m) if @on_message
end
def on_user(m)
super
@thread_group = ThreadGroup.new
@thread_group.add Thread.current
@ctcp_actions = {}
@channels = {}
load_config
@opts = Atig::Option.parse @real
context = OpenStruct.new(log:@log, opts:@opts)
oauth = OAuth.new(context, @nick)
unless oauth.verified? then
channel = channel '#oauth'
channel.join_me
channel.notify "Please approve me at #{oauth.url}"
callcc{|cc|
@on_message = lambda{|x|
if x.command.downcase == 'privmsg' then
_, mesg = *x.params
if oauth.verify(mesg.strip)
channel.part_me "Verified"
save_config
@on_message = nil
cc.call
end
end
return true
}
return
}
end
log :debug, "initialize Twitter"
twitter = Twitter.new context, oauth.access
search = SearchTwitter.new context
if @opts.stream
unless @channels.key?("##{@nick}")
ch = channel("##{@nick}")
ch.join_me
end
end
stream = Stream.new context, @channels["##{@nick}"], oauth.access if @opts.stream
@api = Scheduler.new context, twitter, search, stream
log :debug, "initialize filter"
@ifilters = run_new @@ifilters, context
@ofilters = run_new @@ofilters, context
@api.delay(0) do|t|
me = t.post "account/update_profile"
unless me then
log :info, <<END
Failed to access API.
Please check Twitter Status <http://status.twitter.com/> and try again later.
END
finish
end
@prefix = prefix me
@user = @prefix.user
@host = @prefix.host
post server_name, MODE, @nick, "+o"
@db = Atig::Db::Db.new context, me:me, size: 100, tmpdir: @tmpdir
run_new @@commands, context, self, @api, @db
run_new @@agents , context, @api, @db
run_new @@channels, context, self, @db
@db.statuses.add user: me, source: :me, status: me.status
end
end
def on_disconnected
(@thread_group.list - [Thread.current]).each {|t| t.kill }
end
def on_privmsg(m)
target, mesg = *m.params
m.ctcps.each {|ctcp| on_ctcp(target, ctcp) } if m.ctcp?
case
when mesg.empty?
return
when mesg.sub!(/\A +/, "")
on_ctcp_action(target, mesg)
when target[0] != ?#
channel target
on_ctcp_action(target, "dm #{target} #{mesg}")
when (@opts.old_style_reply and mesg =~ /\A@(?>([A-Za-z0-9_]{1,15}))[^A-Za-z0-9_]/)
on_ctcp_action(target, "reply #{$1} #{mesg}")
else
on_ctcp_action(target, "status #{mesg}")
end
end
def on_ctcp(target, mesg)
if mesg.respond_to? :encoding!
mesg.encoding! "UTF-8"
end
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(" ")
last_match = nil
command = command.to_s.downcase
_, action = @ctcp_actions.find{|define, f|
r = (define === command)
last_match = Regexp.last_match
r
}
if action then
safe {
action.call(target, mesg, last_match || command, args)
}
else
self[target].notify "[atig.rb] CTCP ACTION COMMANDS:"
@ctcp_actions.keys.each do |c|
self[target].notify c.to_s
end
end
end
def on_invite(m)
nick, channel = *m.params
if not nick.screen_name? or @db.me.screen_name.casecmp(nick).zero?
post server_name, ERR_NOSUCHNICK, nick, "No such nick: #{nick}" # or yourself
return
end
unless @channels.key? channel
post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}"
return
end
@api.delay(0){|api| @channels[channel].on_invite(api, nick) }
end
def on_kick(m)
channel, nick, _ = *m.params
if not nick.screen_name? or @db.me.screen_name.casecmp(nick).zero?
post server_name, ERR_NOSUCHNICK, nick, "No such nick: #{nick}" # or yourself
return
end
unless @channels.key? channel
post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}"
return
end
@api.delay(0){|api| @channels[channel].on_kick(api, nick) }
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
on_ctcp_action(nil, "whois #{nick}")
end
def on_topic(m)
channel,topic = *m.params
on_ctcp_action(channel, "topic #{topic}")
end
def on_who(m)
channel = m.params[0]
unless @channels.key? channel
post server_name, ERR_NOSUCHNICK, nick, "No such channel: #{channel}"
return
end
@channels[channel].on_who do|user|
# "<channel> <user> <host> <server> <nick>
# ( "H" / "G" > ["*"] [ ( "@" / "+" ) ]
# :<hopcount> <real name>"
prefix = prefix(user)
server = 'twitter.com'
mode = case prefix.nick
when @nick then "~"
else "+"
end
real = user.name
post server_name, RPL_WHOREPLY, @nick, channel, prefix.user, prefix.host, server, prefix.nick, "H*#{mode}", "1 #{real}"
end
post server_name, RPL_ENDOFWHO, @nick, channel
end
protected
def run_new(klasses,*args)
(klasses || []).map do|klass|
if klass.respond_to?(:new)
klass.new(*args)
else
klass
end
end
end
CONFIG_FILE = File.expand_path("~/.atig/oauth")
def save_config
FileUtils.mkdir_p File.dirname(CONFIG_FILE)
File.open(CONFIG_FILE, "w") {|io|
YAML.dump(OAuth.dump,io)
}
FileUtils.chmod 0600, CONFIG_FILE
end
def load_config
FileUtils.mkdir_p File.dirname(CONFIG_FILE)
OAuth.load(YAML.load_file(CONFIG_FILE)) rescue nil
end
def available_user_modes
"o"
end
def available_channel_modes
"mntiovah"
end
end
end
end