Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

395 lines (344 sloc) 10.015 kB
# -*- coding: utf-8 -*-
require "cinch/target"
require "timeout"
module Cinch
# @attr_reader [String] user
# @attr_reader [String] host
# @attr_reader [String] realname
# @attr_reader [String] authname
# @attr_reader [Integer] idle How long this user has been idle, in seconds.
# This is a snapshot of the last WHOIS.
# @attr_reader [Time] signed_on_at
# @attr_reader [Array<Channel>] channels All channels the user is in.
# @attr_reader [String, nil] away The user's away message, or
# `nil` if not away.
#
# @version 2.0.0
class User < Target
include Syncable
alias_method :nick, :name
# @return [String]
# @since 1.1.0
attr_reader :last_nick
# @return [Boolean]
attr_reader :synced
# @return [Boolean]
attr_reader :in_whois
# @api private
attr_writer :in_whois
# @return [Boolean] True if the instance references an user who
# cannot be found on the server.
attr_reader :unknown
alias_method :unknown?, :unknown
undef_method "unknown?"
undef_method "unknown"
def unknown
self.unknown?
end
# @return [Boolean] True if the user is online.
# @note This attribute will be updated by various events, but
# unless {#monitor} is being used, this information cannot be
# ensured to be always correct.
attr_reader :online
alias_method :online?, :online
undef_method "online?"
undef_method "online"
def online
self.online?
end
# @return [Boolean] True if the user is using a secure connection, i.e. SSL.
attr_reader :secure
alias_method :secure?, :secure
undef_method "secure?"
undef_method "secure"
def secure
self.secure?
end
# By default, you can use methods like User#user, User#host and
# alike – If you however fear that another thread might change
# data while you're using it and if this means a critical issue to
# your code, you can store a clone of the result of this method
# and work with that instead.
#
# @example
# on :channel do |m|
# data = m.user.data.dup
# do_something_with(data.user)
# do_something_with(data.host)
# end
# @return [Hash]
attr_reader :data
# @return [Boolean] True if the user is being monitored
# @see #monitor
# @see #unmonitor
attr_reader :monitored
# @api private
attr_writer :monitored
def initialize(*args)
@data = {
:user => nil,
:host => nil,
:realname => nil,
:authname => nil,
:idle => 0,
:signed_on_at => nil,
:unknown? => false,
:online? => false,
:channels => [],
:secure? => false,
:away => nil,
}
case args.size
when 2
@name, @bot = args
when 4
@data[:user], @name, @data[:host], @bot = args
else
raise ArgumentError
end
@synced_attributes = Set.new
@when_requesting_synced_attribute = lambda {|attr|
unless synced?(attr)
@data[:unknown?] = false
unsync :unknown?
whois
end
}
@monitored = false
end
# Checks if the user is identified. Currently officially supports
# Quakenet and Freenode.
#
# @return [Boolean] true if the user is identified
# @version 1.1.0
def authed?
!attr(:authname).nil?
end
# @see Syncable#attr
def attr(attribute, data = true, unsync = false)
super
end
# Queries the IRC server for information on the user. This will
# set the User's state to not synced. After all information are
# received, the object will be set back to synced.
#
# @return [void]
def whois
return if @in_whois
@data.keys.each do |attr|
unsync attr
end
@in_whois = true
if @bot.irc.network.whois_only_one_argument?
@bot.irc.send "WHOIS #@name"
else
@bot.irc.send "WHOIS #{@bot.irc.server} #@name"
end
end
alias_method :refresh, :whois
# @param [Hash, nil] values A hash of values gathered from WHOIS,
# or `nil` if no data was returned
# @param [Boolean] not_found Has to be true if WHOIS resulted in
# an unknown user
# @return [void]
# @api private
# @since 1.0.1
def end_of_whois(values, not_found = false)
@in_whois = false
if not_found
sync(:unknown?, true, true)
self.online = false
sync(:idle, 0, true)
sync(:channels, [], true)
fields = @data.keys
fields.delete(:unknown?)
fields.delete(:idle)
fields.delete(:channels)
fields.each do |field|
sync(field, nil, true)
end
return
end
if values.nil?
# for some reason, we did not receive user information. one
# reason is freenode throttling WHOIS
Thread.new do
sleep 2
whois
end
return
end
{
:authname => nil,
:idle => 0,
:secure? => false,
:channels => [],
}.merge(values).each do |attr, value|
sync(attr, value, true)
end
sync(:unknown?, false, true)
self.online = true
end
# @return [void]
# @since 1.0.1
# @api private
# @see Syncable#unsync_all
def unsync_all
super
end
# @return [String]
def to_s
@name
end
# @return [String]
def inspect
"#<User nick=#{@name.inspect}>"
end
# Generates a mask for the user.
#
# @param [String] s a pattern for generating the mask.
#
# - %n = nickname
# - %u = username
# - %h = host
# - %r = realname
# - %a = authname
#
# @return [Mask]
def mask(s = "%n!%u@%h")
s = s.gsub(/%(.)/) {
case $1
when "n"
@name
when "u"
self.user
when "h"
self.host
when "r"
self.realname
when "a"
self.authname
end
}
Mask.new(s)
end
# Check if the user matches a mask.
#
# @param [Ban, Mask, User, String] other The user or mask to match against
# @return [Boolean]
def match(other)
Mask.from(other) =~ Mask.from(self)
end
alias_method :=~, :match
# Starts monitoring a user's online state by either using MONITOR
# or periodically running WHOIS.
#
# @since 2.0.0
# @return [void]
# @see #unmonitor
def monitor
if @bot.irc.isupport["MONITOR"] > 0
@bot.irc.send "MONITOR + #@name"
else
refresh
@monitored_timer = Timer.new(@bot, interval: 30) {
refresh
}
@monitored_timer.start
end
@monitored = true
end
# Stops monitoring a user's online state.
#
# @since 2.0.0
# @return [void]
# @see #monitor
def unmonitor
if @bot.irc.isupport["MONITOR"] > 0
@bot.irc.send "MONITOR - #@name"
else
@monitored_timer.stop if @monitored_timer
end
@monitored = false
end
# Send data via DCC SEND to a user.
#
# @param [DCC::DCCableObject] io
# @param [String] filename
# @since 2.0.0
# @return [void]
# @note This method blocks.
def dcc_send(io, filename = File.basename(io.path))
own_ip = bot.config.dcc.own_ip || @bot.irc.socket.addr[2]
dcc = DCC::Outgoing::Send.new(receiver: self,
filename: filename,
io: io,
own_ip: own_ip
)
dcc.start_server
handler = Handler.new(@bot, :message,
Pattern.new(/^/,
/\001DCC RESUME #{filename} #{dcc.port} (\d+)\001/,
/$/)) do |m, position|
next unless m.user == self
dcc.seek(position.to_i)
m.user.send "\001DCC ACCEPT #{filename} #{dcc.port} #{position}\001"
handler.unregister
end
@bot.handlers.register(handler)
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: waiting" % [filename, io.size, own_ip, dcc.port]
dcc.send_handshake
begin
dcc.listen
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: done" % [filename, io.size, own_ip, dcc.port]
rescue Timeout::Error
@bot.loggers.info "DCC: Outgoing DCC SEND: File name: %s - Size: %dB - IP: %s - Port: %d - Status: failed (timeout)" % [filename, io.size, own_ip, dcc.port]
ensure
handler.unregister
end
end
# Updates the user's online state and dispatch the correct event.
#
# @since 2.0.0
# @return [void]
# @api private
def online=(bool)
notify = self.__send__("online?_unsynced") != bool && @monitored
sync(:online?, bool, true)
return unless notify
if bool
@bot.handlers.dispatch(:online, nil, self)
else
@bot.handlers.dispatch(:offline, nil, self)
end
end
# Used to update the user's nick on nickchange events.
#
# @param [String] new_nick The user's new nick
# @api private
# @return [void]
def update_nick(new_nick)
@last_nick, @name = @name, new_nick
@bot.user_list.update_nick(self)
end
# Provides synced access to user attributes.
def method_missing(m, *args)
if m.to_s =~ /^(.+)_unsynced$/
m = $1.to_sym
unsync = true
end
if @data.has_key?(m)
attr(m, true, unsync)
else
super
end
end
# @since 1.1.2
def respond_to?(m)
if m.to_s =~ /^(.+)_unsynced$/
m = $1.to_sym
end
return @data.has_key?(m) || super
end
end
end
Jump to Line
Something went wrong with that request. Please try again.