palbo-lijab 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/lijab +9 -0
- data/ext/extconf.rb +20 -0
- data/ext/readline_extra.c +172 -0
- data/lib/lijab/commands/contacts.rb +101 -0
- data/lib/lijab/commands/options.rb +49 -0
- data/lib/lijab/commands/simple.rb +132 -0
- data/lib/lijab/commands/status.rb +63 -0
- data/lib/lijab/commands/subscription.rb +78 -0
- data/lib/lijab/commands.rb +139 -0
- data/lib/lijab/config.rb +174 -0
- data/lib/lijab/contacts.rb +318 -0
- data/lib/lijab/history.rb +122 -0
- data/lib/lijab/hooks.rb +109 -0
- data/lib/lijab/input.rb +234 -0
- data/lib/lijab/main.rb +246 -0
- data/lib/lijab/out.rb +173 -0
- data/lib/lijab/term/ansi.rb +20 -0
- data/lib/lijab/version.rb +4 -0
- data/lib/lijab/xmpp4r/message.rb +45 -0
- data/lib/lijab.rb +7 -0
- data/lib/readline/extra.rb +7 -0
- metadata +102 -0
@@ -0,0 +1,139 @@
|
|
1
|
+
|
2
|
+
module Lijab
|
3
|
+
|
4
|
+
module Commands
|
5
|
+
@registered = {}
|
6
|
+
@overloaded = {}
|
7
|
+
|
8
|
+
module CommandMixin
|
9
|
+
def define_meta(*names)
|
10
|
+
class_eval do
|
11
|
+
names.each do |name|
|
12
|
+
define_method(name) do |*args|
|
13
|
+
if args.size == 0
|
14
|
+
instance_variable_get("@#{name}")
|
15
|
+
else
|
16
|
+
instance_variable_set("@#{name}", *args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
module ContactCompleterMixin
|
25
|
+
def completer(line)
|
26
|
+
_, contact = line.split(nil, 2)
|
27
|
+
Main.contacts.completer(contact, false)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class CommandError < RuntimeError
|
32
|
+
end
|
33
|
+
|
34
|
+
class Command
|
35
|
+
class << self
|
36
|
+
private :new
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.define(name, &block)
|
40
|
+
c = new
|
41
|
+
c.instance_eval(&block)
|
42
|
+
Commands::register(name.to_sym, c)
|
43
|
+
end
|
44
|
+
|
45
|
+
def completer(s)
|
46
|
+
end
|
47
|
+
|
48
|
+
def run(args)
|
49
|
+
end
|
50
|
+
|
51
|
+
extend CommandMixin
|
52
|
+
define_meta :usage, :description
|
53
|
+
end
|
54
|
+
|
55
|
+
module_function
|
56
|
+
|
57
|
+
def init
|
58
|
+
files = Dir[File.join(File.dirname(File.expand_path(__FILE__)), 'commands', '*.rb')] + \
|
59
|
+
Dir[File.join(Config.dirs[:commands], '**', '*.rb')]
|
60
|
+
|
61
|
+
files.each { |f| load f }
|
62
|
+
Config.opts[:aliases].each do |a, c|
|
63
|
+
register_alias(a, c)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def register(name, cmd)
|
68
|
+
name = name.to_sym
|
69
|
+
@overloaded[name] = @registered[name] if @registered.key?(name)
|
70
|
+
@registered[name] = cmd
|
71
|
+
end
|
72
|
+
|
73
|
+
def register_alias(name, s)
|
74
|
+
alias_cmd, alias_args = s.split(" ", 2)
|
75
|
+
alias_cmd.strip!
|
76
|
+
alias_cmd = alias_cmd[1..-1] if alias_cmd[0] == ?/
|
77
|
+
name = name[1..-1] if name[0] == ?/
|
78
|
+
|
79
|
+
Command.define name.to_sym do
|
80
|
+
description %{Alias for "#{s}"}
|
81
|
+
@alias_cmd = alias_cmd
|
82
|
+
@alias_args = alias_args
|
83
|
+
@name = name
|
84
|
+
|
85
|
+
def run(args)
|
86
|
+
Commands::run(@alias_cmd, [@alias_args, args].join(" "), true)
|
87
|
+
end
|
88
|
+
|
89
|
+
def completer(line)
|
90
|
+
args = line.split(" ", 2)[1]
|
91
|
+
Commands::completer("/#{@alias_cmd} #{@alias_args} #{args}").map do |r|
|
92
|
+
r.gsub(/\/#{@alias_cmd}\s?/, "")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def get(name)
|
99
|
+
@registered[name.to_sym]
|
100
|
+
end
|
101
|
+
|
102
|
+
def registered?(name)
|
103
|
+
@registered.key?(name.to_sym)
|
104
|
+
end
|
105
|
+
|
106
|
+
def run(cmd, args="", is_alias=false)
|
107
|
+
cmd = cmd.strip.to_sym
|
108
|
+
command = @overloaded[cmd] if is_alias
|
109
|
+
command ||= @registered[cmd]
|
110
|
+
if command
|
111
|
+
begin
|
112
|
+
command.run(args.strip)
|
113
|
+
rescue CommandError => e
|
114
|
+
Out::error("#{cmd}: #{e}", false)
|
115
|
+
end
|
116
|
+
else
|
117
|
+
Out::error("no such command: /#{cmd}", false)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def completer(line)
|
122
|
+
cmd, args = line[1..-1].split(" ", 2).strip
|
123
|
+
cmd ||= ""
|
124
|
+
|
125
|
+
matches = @registered.keys.find_all { |c| c.to_s.match(/^#{Regexp.escape(cmd)}/) }
|
126
|
+
|
127
|
+
if !cmd.empty? && (matches.length == 1 || args) && registered?(cmd)
|
128
|
+
(@registered[cmd.to_sym].completer(line) || []).map { |s| "/#{cmd} #{s}" }
|
129
|
+
else
|
130
|
+
matches.map { |k| "/#{k}" }
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
attr_reader :registered
|
135
|
+
module_function :registered
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
data/lib/lijab/config.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
|
2
|
+
module Lijab
|
3
|
+
|
4
|
+
module Config
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def init(args)
|
8
|
+
@opts = {}
|
9
|
+
@dirs = {}
|
10
|
+
@files = {}
|
11
|
+
@accounts = []
|
12
|
+
@account = nil
|
13
|
+
|
14
|
+
setup_basedir(args[:basedir])
|
15
|
+
read_accounts(args[:account])
|
16
|
+
read_options()
|
17
|
+
|
18
|
+
@jid = Jabber::JID.new("#{@account[:jabberid]}")
|
19
|
+
@jid.resource ||= "lijab#{(0...5).map{rand(10).to_s}.join}"
|
20
|
+
@account[:server] ||= @jid.domain
|
21
|
+
|
22
|
+
create_account_dirs()
|
23
|
+
end
|
24
|
+
|
25
|
+
def setup_basedir(basedir)
|
26
|
+
# xdg? meh
|
27
|
+
#xdg = ENV["XDG_CONFIG_HOME"]
|
28
|
+
#xdg && File.join(xdg, "lijab") ||
|
29
|
+
|
30
|
+
@basedir = basedir ||
|
31
|
+
ENV["LIJABDIR"] ||
|
32
|
+
"~/.lijab"
|
33
|
+
@basedir = File.expand_path(@basedir)
|
34
|
+
|
35
|
+
unless File.directory?(@basedir)
|
36
|
+
puts "Creating #{@basedir} with the default configs"
|
37
|
+
end
|
38
|
+
|
39
|
+
%w{accounts commands hooks}.each do |d|
|
40
|
+
@dirs[d.to_sym] = path = File.join(@basedir, d)
|
41
|
+
FileUtils.mkdir_p(path)
|
42
|
+
end
|
43
|
+
|
44
|
+
@files[:accounts] = path = File.join(@basedir, "accounts.yml")
|
45
|
+
File.open(path, 'w') { |f| f.puts(DEFAULT_ACCOUNTS_FILE) } unless File.file?(path)
|
46
|
+
|
47
|
+
@files[:config] = File.join(@basedir, "config.yml")
|
48
|
+
dump_config_file(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
def dump_config_file(default=false, clobber=false)
|
52
|
+
if !File.file?(@files[:config]) || clobber
|
53
|
+
File.open(@files[:config], 'w') do |f|
|
54
|
+
DEFAULT_OPTIONS.each do |a|
|
55
|
+
if a[2]
|
56
|
+
f.puts
|
57
|
+
a[2].each { |l| f.puts("# #{l}") }
|
58
|
+
end
|
59
|
+
v = default ? a[1] : @opts[a[0]]
|
60
|
+
f.puts(YAML.dump({a[0] => v})[5..-1].chomp)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def read_accounts(account)
|
67
|
+
File.open(@files[:accounts]) do |f|
|
68
|
+
YAML.load_documents(f) { |a| @accounts << a }
|
69
|
+
end
|
70
|
+
|
71
|
+
errors = []
|
72
|
+
errors << "need at least one account!" if @accounts.empty?
|
73
|
+
|
74
|
+
@accounts.each do |a|
|
75
|
+
a[:port] ||= a[:use_ssl] ? 5223 : 5222
|
76
|
+
|
77
|
+
errors << "account #{a} needs a name" unless a.key?(:name)
|
78
|
+
errors << "account #{a[:name] || a} needs a jabberid" unless a.key?(:jabberid)
|
79
|
+
end
|
80
|
+
|
81
|
+
@account = account ? @accounts.find { |a| a[:name] == account} : @accounts[0]
|
82
|
+
|
83
|
+
errors << "no account with name #{account} in #{@accounts_file}" if account && !@account
|
84
|
+
|
85
|
+
errors.each do |e|
|
86
|
+
STDERR.puts("#{File.basename($0)}: error: #{e}")
|
87
|
+
end
|
88
|
+
|
89
|
+
exit(1) unless errors.empty?
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_options
|
93
|
+
# FIXME: error check / validate
|
94
|
+
|
95
|
+
@opts = Hash[*DEFAULT_OPTIONS.collect { |a| [a[0], a[1]] }.flatten]
|
96
|
+
@opts.merge!(YAML.load_file(@files[:config]))
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_account_dirs
|
100
|
+
@accounts.each do |a|
|
101
|
+
a[:dir] = File.join(@dirs[:accounts], @jid.strip.to_s)
|
102
|
+
a[:log_dir] = File.join(a[:dir], "logs")
|
103
|
+
a[:typed] = File.join(a[:dir], "typed_history")
|
104
|
+
|
105
|
+
[:dir, :log_dir].each { |s| FileUtils.mkdir_p(a[s]) }
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
DEFAULT_OPTIONS = [
|
110
|
+
[:datetime_format, "%H:%M:%S", ["Time formatting (leave empty to disable timestamps)"]],
|
111
|
+
[:history_datetime_format, "%Y-%b-%d %H:%M:%S"],
|
112
|
+
[:autocomplete_online_first, true,
|
113
|
+
["When completing contacts try to find matches for online contacts, and if none",
|
114
|
+
"is found try to find matches on all of them. Otherwise always match every",
|
115
|
+
"contact."]],
|
116
|
+
[:show_status_changes, true, ["Show changes in contacts' status"]],
|
117
|
+
[:show_groups_in_contact_list, false, ["Group contacts in contact list"]],
|
118
|
+
[:ctrl_c_quits, false,
|
119
|
+
["ctrl+c quits the program if enabled, otherwise ctrl+c ignores whatever is",
|
120
|
+
"typed and you get a clean prompt, and ctrl+d on a clean line exits lijab,",
|
121
|
+
"terminal style."]],
|
122
|
+
[:terminal_bell_on_message, true,
|
123
|
+
["Ring the terminal bell on incoming message.",
|
124
|
+
"Useful for setting the urgent hint on the terminal window:",
|
125
|
+
"Set as so in your ~/.Xdefaults, might have to run xrdb -merge ~/.Xdefaults afterwards",
|
126
|
+
"XTerm*bellIsUrgent: true",
|
127
|
+
"or",
|
128
|
+
"URxvt*urgentOnBell: true",
|
129
|
+
"or just look it up on your terminal's man page, don't be lazy."]],
|
130
|
+
[:status_priorities,
|
131
|
+
{:chat => 55, :available => 50, :away => 40, :xa => 30, :dnd => 20},
|
132
|
+
["Default priority for each status"]],
|
133
|
+
[:aliases, {"/h" => "/history", "/exit" => "/quit"},
|
134
|
+
["Command aliases.",
|
135
|
+
"<command_alias> : <existing_command>",
|
136
|
+
"Commands can be overloaded.",
|
137
|
+
"For instance /who could be redefined like so to sort by status by default.",
|
138
|
+
"/who : /who status"]]
|
139
|
+
]
|
140
|
+
|
141
|
+
DEFAULT_ACCOUNTS_FILE = %Q{
|
142
|
+
# Accounts go here. Separate each one with ---
|
143
|
+
# First one is the default.
|
144
|
+
|
145
|
+
#---
|
146
|
+
#:name : an_account # the account name
|
147
|
+
#:jabberid : fisk@example.com/lijab # the resource is optional
|
148
|
+
#:password : frosk # optional, will prompt if not present
|
149
|
+
#:server : localhost # optional, will use the jid domain if not present
|
150
|
+
#:port : 5222 # optional
|
151
|
+
#:use_ssl : no # deprecated in jabber, but might help sometimes
|
152
|
+
#:log : yes # yes|no ; default no
|
153
|
+
|
154
|
+
#---
|
155
|
+
#:name : another_account
|
156
|
+
#:jabberid : another_user@example.com/lijab
|
157
|
+
|
158
|
+
#---
|
159
|
+
#:name : gmail_account
|
160
|
+
#:jabberid : blah@gmail.com/lijab
|
161
|
+
#:server : talk.google.com
|
162
|
+
## might wanna try use_ssl if the normal settings don't work (e.g. in ubuntu afaik)
|
163
|
+
##:port : 5223
|
164
|
+
##:use_ssl : yes
|
165
|
+
#:log : yes
|
166
|
+
|
167
|
+
}.gsub!(/^ */, '')
|
168
|
+
|
169
|
+
attr_reader :jid, :account, :basedir, :dirs, :files, :opts
|
170
|
+
module_function :jid, :account, :basedir, :dirs, :files, :opts
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
@@ -0,0 +1,318 @@
|
|
1
|
+
|
2
|
+
class Jabber::Presence
|
3
|
+
PRETTY = Hash.new(["", []]).update(
|
4
|
+
{ :available => ["available", [:green, :bold]],
|
5
|
+
:away => ["away", [:magenta]],
|
6
|
+
:chat => ["chatty", [:green]],
|
7
|
+
:dnd => ["busy", [:red, :bold]],
|
8
|
+
:xa => ["not available", [:red]],
|
9
|
+
:offline => ["offline", [:blue]]
|
10
|
+
})
|
11
|
+
|
12
|
+
def pretty_show
|
13
|
+
case type()
|
14
|
+
when nil
|
15
|
+
show() || :available
|
16
|
+
when :unavailable
|
17
|
+
:offline
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def pretty(colorize=false)
|
22
|
+
sh = pretty_show()
|
23
|
+
|
24
|
+
s, colors = PRETTY[sh]
|
25
|
+
s = s.colored(*colors) if colorize
|
26
|
+
message = status() && !status().empty? ? " [#{status()}]" : ""
|
27
|
+
"#{s}#{message}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Lijab
|
32
|
+
module Contacts
|
33
|
+
|
34
|
+
class Contact
|
35
|
+
attr_accessor :simple_name, :history
|
36
|
+
attr_writer :color
|
37
|
+
attr_reader :roster_item
|
38
|
+
|
39
|
+
COLORS = [:red, :blue, :yellow, :green, :magenta, :cyan].sort_by { rand() }
|
40
|
+
@@cur_color = 0
|
41
|
+
|
42
|
+
def initialize(simple_name, roster_item)
|
43
|
+
@simple_name = simple_name
|
44
|
+
@roster_item = roster_item
|
45
|
+
@resource_jid = @roster_item.jid
|
46
|
+
@history = HistoryHandler::get(jid())
|
47
|
+
end
|
48
|
+
|
49
|
+
def presence(jid=nil)
|
50
|
+
if jid
|
51
|
+
p = @roster_item.presences.select { |p| p.jid == jid }.first
|
52
|
+
else
|
53
|
+
p = @roster_item.presences.inject(nil) do |max, presence|
|
54
|
+
!max.nil? && max.priority.to_i > presence.priority.to_i ? max : presence
|
55
|
+
end
|
56
|
+
end
|
57
|
+
p || Jabber::Presence.new.set_type(:unavailable)
|
58
|
+
end
|
59
|
+
|
60
|
+
def handle_message(msg)
|
61
|
+
@thread = msg.thread
|
62
|
+
|
63
|
+
if msg.body && !msg.body.empty?
|
64
|
+
@resource_jid = msg.from
|
65
|
+
Out::message_in(@simple_name, msg.body, [color(), :bold])
|
66
|
+
@history.log(msg.body, :from)
|
67
|
+
end
|
68
|
+
|
69
|
+
if msg.chat_state
|
70
|
+
s = ""
|
71
|
+
case msg.chat_state
|
72
|
+
when :composing
|
73
|
+
s = "is typing"
|
74
|
+
when :active
|
75
|
+
Out::clear_infoline
|
76
|
+
when :gone
|
77
|
+
s = "went away"
|
78
|
+
when :paused
|
79
|
+
s = "paused typing"
|
80
|
+
end
|
81
|
+
Out::infoline("* #{@simple_name} #{s}".red) unless s.empty?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def send_message(msg, jid=nil)
|
86
|
+
if msg.kind_of?(Jabber::Message)
|
87
|
+
msg.thread = @thread unless msg.thread
|
88
|
+
message = msg
|
89
|
+
elsif msg.kind_of?(String) && !msg.empty?
|
90
|
+
# TODO: send chat_state only in the first message
|
91
|
+
if jid
|
92
|
+
@resource_jid = jid
|
93
|
+
else
|
94
|
+
jid = @resource_jid
|
95
|
+
end
|
96
|
+
Out::message_out(@simple_name, msg, color())
|
97
|
+
message = Jabber::Message.new(jid, msg).set_type(:chat) \
|
98
|
+
.set_chat_state(:active) \
|
99
|
+
.set_thread(@thread)
|
100
|
+
|
101
|
+
@chat_state_timer.kill if @chat_state_timer && @chat_state_timer.alive?
|
102
|
+
end
|
103
|
+
|
104
|
+
message = HooksHandler::handle_pre_send_message(self, message)
|
105
|
+
return unless message
|
106
|
+
|
107
|
+
Main.client.send(message)
|
108
|
+
|
109
|
+
HooksHandler::handle_post_send_message(self, message)
|
110
|
+
|
111
|
+
@history.log(message.body, :to)
|
112
|
+
@chat_state = :active
|
113
|
+
end
|
114
|
+
|
115
|
+
def send_chat_state(state)
|
116
|
+
return if state == @chat_state
|
117
|
+
msg = Jabber::Message.new(jid(), nil).set_type(:chat).set_chat_state(state)
|
118
|
+
Main.client.send(msg)
|
119
|
+
@chat_state = state
|
120
|
+
end
|
121
|
+
|
122
|
+
def typed_stuff
|
123
|
+
send_chat_state(:composing)
|
124
|
+
|
125
|
+
@chat_state_timer.kill if @chat_state_timer && @chat_state_timer.alive?
|
126
|
+
@chat_state_timer = Thread.new do
|
127
|
+
sleep(3); return if !Main.connected
|
128
|
+
send_chat_state(:paused)
|
129
|
+
sleep(10); return if !Main.connected
|
130
|
+
send_chat_state(:active)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def presence_changed(old_p, new_p)
|
135
|
+
@resource_jid = @roster_item.jid if old_p && old_p.from == @resource_jid
|
136
|
+
end
|
137
|
+
|
138
|
+
def jid
|
139
|
+
@roster_item.jid
|
140
|
+
end
|
141
|
+
|
142
|
+
def online?
|
143
|
+
@roster_item.online?
|
144
|
+
end
|
145
|
+
|
146
|
+
def color
|
147
|
+
@color = COLORS[(@@cur_color = (@@cur_color + 1) % COLORS.length)] unless @color
|
148
|
+
@color
|
149
|
+
end
|
150
|
+
|
151
|
+
def to_s;
|
152
|
+
@simple_name
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
class Contacts < Hash
|
157
|
+
attr_reader :roster, :subscription_requests
|
158
|
+
|
159
|
+
def initialize(roster)
|
160
|
+
super()
|
161
|
+
@roster = roster
|
162
|
+
|
163
|
+
# why does everything always has to be so hackish?
|
164
|
+
self_ri = Jabber::Roster::Helper::RosterItem.new(Main.client)
|
165
|
+
self_ri.jid = Config.jid.strip
|
166
|
+
@roster.items[self_ri.jid] = self_ri
|
167
|
+
|
168
|
+
@roster.add_presence_callback(&method(:handle_presence))
|
169
|
+
@roster.add_subscription_callback(&method(:handle_subscription))
|
170
|
+
@roster.add_subscription_request_callback(&method(:handle_subscription))
|
171
|
+
@roster.wait_for_roster
|
172
|
+
|
173
|
+
@subscription_requests = {}
|
174
|
+
@short = {}
|
175
|
+
|
176
|
+
@roster.items.each do |jid, item|
|
177
|
+
add(jid, Contact.new(jid.node, item))
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def [](k)
|
182
|
+
return @short[k] if @short.key?(k)
|
183
|
+
|
184
|
+
k = Jabber::JID.new(k) unless k.is_a?(Jabber::JID)
|
185
|
+
|
186
|
+
super(k) || super(k.strip)
|
187
|
+
end
|
188
|
+
|
189
|
+
def key?(k)
|
190
|
+
return true if @short.key?(k)
|
191
|
+
|
192
|
+
k = Jabber::JID.new(k) unless k.is_a?(Jabber::JID)
|
193
|
+
|
194
|
+
super(k) || super(k.strip)
|
195
|
+
end
|
196
|
+
|
197
|
+
def add(jid, contact=nil)
|
198
|
+
if contact
|
199
|
+
self[jid] = contact
|
200
|
+
if @short.key?(jid.node)
|
201
|
+
self[@short[jid.node].jid].simple_name = jid.strip.to_s
|
202
|
+
@short[@short[jid.node].jid.strip.to_s] = @short.delete(jid.node)
|
203
|
+
@short[jid.strip.to_s] = self[jid]
|
204
|
+
else
|
205
|
+
@short[jid.node] = self[jid]
|
206
|
+
end
|
207
|
+
else
|
208
|
+
jid = Jabber::JID.new(jid) unless jid.is_a?(Jabber::JID)
|
209
|
+
jid.strip!
|
210
|
+
|
211
|
+
@roster.add(jid, nil, true)
|
212
|
+
end
|
213
|
+
|
214
|
+
contact || self[jid]
|
215
|
+
end
|
216
|
+
|
217
|
+
def remove(jid)
|
218
|
+
return false unless key?(jid)
|
219
|
+
|
220
|
+
contact = self[jid]
|
221
|
+
contact.roster_item.remove()
|
222
|
+
@short.delete(contact.simple_name)
|
223
|
+
self.delete(contact.jid)
|
224
|
+
|
225
|
+
true
|
226
|
+
end
|
227
|
+
|
228
|
+
def process_request(jid, action)
|
229
|
+
jid = Jabber::JID.new(jid) unless jid.is_a?(Jabber::JID)
|
230
|
+
jid.strip!
|
231
|
+
if @subscription_requests.include?(jid)
|
232
|
+
|
233
|
+
@subscription_requests.delete(jid)
|
234
|
+
case action
|
235
|
+
when :accept
|
236
|
+
Main.contacts.roster.accept_subscription(jid)
|
237
|
+
when :decline
|
238
|
+
Main.contacts.roster.decline_subscription(jid)
|
239
|
+
end
|
240
|
+
|
241
|
+
true
|
242
|
+
else
|
243
|
+
false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def has_subscription_requests?
|
248
|
+
!@subscription_requests.empty?
|
249
|
+
end
|
250
|
+
|
251
|
+
def subscription_requests
|
252
|
+
@subscription_requests.keys.map { |jid| jid.to_s }
|
253
|
+
end
|
254
|
+
|
255
|
+
def completer(line, end_with_colon=true)
|
256
|
+
if line.include?(?@)
|
257
|
+
matches = @roster.items.values.collect { |ri| ri.presences }.flatten.select do |p|
|
258
|
+
p.from.to_s =~ /^#{Regexp.escape(line)}/
|
259
|
+
end.map { |p| p.from.to_s }
|
260
|
+
else
|
261
|
+
if Config.opts[:autocomplete_online_first]
|
262
|
+
matches = @short.keys.find_all do |name|
|
263
|
+
@short[name].online? && name =~ /^#{Regexp.escape(line)}/
|
264
|
+
end
|
265
|
+
end
|
266
|
+
if matches.empty? || !Config.opts[:autocomplete_online_first]
|
267
|
+
matches = @short.keys.find_all { |name| name =~ /^#{Regexp.escape(line)}/ }
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end_with_colon && matches.length == 1 ? "#{matches.first}:" : matches
|
271
|
+
end
|
272
|
+
|
273
|
+
def handle_non_contact_message(msg)
|
274
|
+
# TODO: improve this, maybe show the contact differentiated in /contacts
|
275
|
+
|
276
|
+
jid = msg.from.strip
|
277
|
+
|
278
|
+
ri = Jabber::Roster::Helper::RosterItem.new(Main.client)
|
279
|
+
ri.jid = jid
|
280
|
+
|
281
|
+
self[jid] = Contact.new(jid.to_s, ri)
|
282
|
+
self[jid].handle_message(msg)
|
283
|
+
#add(jid, Contact.new(jid.to_s, ri)).handle_message(msg)
|
284
|
+
end
|
285
|
+
|
286
|
+
def handle_presence(roster_item, old_p, new_p)
|
287
|
+
contact = self[new_p.from]
|
288
|
+
if Config.opts[:show_status_changes]
|
289
|
+
type = new_p.type
|
290
|
+
if type == nil || type == :unavailable && (!contact || !contact.online?)
|
291
|
+
Out::presence(new_p.from.to_s, new_p)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
if contact
|
296
|
+
contact.roster_item.presences.delete_if { |p| p.type == :unavailable } rescue nil
|
297
|
+
contact.presence_changed(old_p, new_p)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def handle_subscription(roster_item, presence)
|
302
|
+
show = true
|
303
|
+
if presence.type == :subscribe
|
304
|
+
show = !@subscription_requests.key?(presence.from.strip)
|
305
|
+
@subscription_requests[presence.from.strip] = presence
|
306
|
+
elsif presence.type == :subscribed
|
307
|
+
jid = presence.from.strip
|
308
|
+
|
309
|
+
@roster.add(jid)
|
310
|
+
add(jid, Contact.new(jid.node, @roster[jid]))
|
311
|
+
#p = Jabber::Presence.new.set_type(:probe)
|
312
|
+
end
|
313
|
+
|
314
|
+
Out::subscription(presence.from.to_s, presence.type, presence.status) if show
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|