lijab 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,324 @@
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
+ return unless jid.node # XXX: apparently transports have no node, do something here?
199
+
200
+ if contact
201
+ self[jid] = contact
202
+ if @short.key?(jid.node)
203
+ prev = @short[jid.node]
204
+
205
+ self[jid].simple_name = jid.strip.to_s
206
+ self[prev.jid].simple_name = prev.jid.strip.to_s
207
+
208
+ @short[prev.jid.strip.to_s] = @short.delete(jid.node)
209
+ @short[jid.strip.to_s] = self[jid]
210
+ else
211
+ @short[jid.node] = self[jid]
212
+ end
213
+ else
214
+ jid = Jabber::JID.new(jid) unless jid.is_a?(Jabber::JID)
215
+ jid.strip!
216
+
217
+ @roster.add(jid, nil, true)
218
+ end
219
+
220
+ contact || self[jid]
221
+ end
222
+
223
+ def remove(jid)
224
+ return false unless key?(jid)
225
+
226
+ contact = self[jid]
227
+ contact.roster_item.remove()
228
+ @short.delete(contact.simple_name)
229
+ self.delete(contact.jid)
230
+
231
+ true
232
+ end
233
+
234
+ def process_request(jid, action)
235
+ jid = Jabber::JID.new(jid) unless jid.is_a?(Jabber::JID)
236
+ jid.strip!
237
+ if @subscription_requests.include?(jid)
238
+
239
+ @subscription_requests.delete(jid)
240
+ case action
241
+ when :accept
242
+ Main.contacts.roster.accept_subscription(jid)
243
+ when :decline
244
+ Main.contacts.roster.decline_subscription(jid)
245
+ end
246
+
247
+ true
248
+ else
249
+ false
250
+ end
251
+ end
252
+
253
+ def has_subscription_requests?
254
+ !@subscription_requests.empty?
255
+ end
256
+
257
+ def subscription_requests
258
+ @subscription_requests.keys.map { |jid| jid.to_s }
259
+ end
260
+
261
+ def completer(line, end_with_colon=true)
262
+ if line.include?(?@)
263
+ matches = @roster.items.values.collect { |ri| ri.presences }.flatten.select do |p|
264
+ p.from.to_s =~ /^#{Regexp.escape(line)}/
265
+ end.map { |p| p.from.to_s }
266
+ else
267
+ if Config.opts[:autocomplete_online_first]
268
+ matches = @short.keys.find_all do |name|
269
+ @short[name].online? && name =~ /^#{Regexp.escape(line)}/
270
+ end
271
+ end
272
+ if matches.empty? || !Config.opts[:autocomplete_online_first]
273
+ matches = @short.keys.find_all { |name| name =~ /^#{Regexp.escape(line)}/ }
274
+ end
275
+ end
276
+ end_with_colon && matches.length == 1 ? "#{matches.first}:" : matches
277
+ end
278
+
279
+ def handle_non_contact_message(msg)
280
+ # TODO: improve this, maybe show the contact differentiated in /contacts
281
+
282
+ jid = msg.from.strip
283
+
284
+ ri = Jabber::Roster::Helper::RosterItem.new(Main.client)
285
+ ri.jid = jid
286
+
287
+ self[jid] = Contact.new(jid.to_s, ri)
288
+ self[jid].handle_message(msg)
289
+ #add(jid, Contact.new(jid.to_s, ri)).handle_message(msg)
290
+ end
291
+
292
+ def handle_presence(roster_item, old_p, new_p)
293
+ contact = self[new_p.from]
294
+ if Config.opts[:show_status_changes]
295
+ type = new_p.type
296
+ if type == nil || type == :unavailable && (!contact || !contact.online?)
297
+ Out::presence(new_p.from.to_s, new_p)
298
+ end
299
+ end
300
+
301
+ if contact
302
+ contact.roster_item.presences.delete_if { |p| p.type == :unavailable } rescue nil
303
+ contact.presence_changed(old_p, new_p)
304
+ end
305
+ end
306
+
307
+ def handle_subscription(roster_item, presence)
308
+ show = true
309
+ if presence.type == :subscribe
310
+ show = !@subscription_requests.key?(presence.from.strip)
311
+ @subscription_requests[presence.from.strip] = presence
312
+ elsif presence.type == :subscribed
313
+ jid = presence.from.strip
314
+
315
+ @roster.add(jid)
316
+ add(jid, Contact.new(jid.node, @roster[jid]))
317
+ #p = Jabber::Presence.new.set_type(:probe)
318
+ end
319
+
320
+ Out::subscription(presence.from.to_s, presence.type, presence.status) if show
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,122 @@
1
+ require 'time'
2
+
3
+ class File
4
+ include File::Tail
5
+ end
6
+
7
+ module Lijab
8
+
9
+ module HistoryHandler
10
+ class DummyHistory
11
+ def log(*a)
12
+ end
13
+
14
+ def last(*a)
15
+ Out::put("warning: logs are disabled")
16
+ []
17
+ end
18
+ end
19
+
20
+ class History
21
+ MEMORY_LOG_LENGTH = 50
22
+
23
+ def initialize(path, target=nil, log_to_session=false)
24
+ @path, @target, @log_to_session = path, target, log_to_session
25
+ @m = []
26
+ end
27
+
28
+ def init_logfile
29
+ @w = File.open(@path, 'a')
30
+ @r = File.open(@path, 'r')
31
+ @r.return_if_eof = true
32
+ end
33
+
34
+ def log(msg, direction, target=nil)
35
+ init_logfile() unless @w
36
+
37
+ time = Time.now.utc
38
+ target ||= @target
39
+ arrow = direction == :from ? "<-" : "->"
40
+ quoted = [msg].pack("M").gsub(/=?\n/) { |m| m[0] == ?= ? "" : "=0A" }
41
+
42
+ @w.puts("#{time.iso8601} #{target} #{arrow} #{quoted}")
43
+ @w.flush
44
+
45
+ @m.push({:time=>time.localtime, :target=>target, :direction=>direction, :msg=>msg})
46
+ @m.shift if @m.length > MEMORY_LOG_LENGTH
47
+
48
+ @m = []
49
+
50
+ HistoryHandler::log(msg, direction, target) if @log_to_session
51
+
52
+ self
53
+ end
54
+
55
+ def last(n)
56
+ return [] if n <= 0
57
+
58
+ init_logfile() unless @r
59
+
60
+ if n <= @m.length
61
+ @m[-n..-1]
62
+ else
63
+ ret = []
64
+ @r.seek(0, File::SEEK_END)
65
+ @r.backward(n)
66
+ @r.tail(n-@m.length) do |l|
67
+ time, target, direction, msg = l.split(" ", 4)
68
+ ret << {:time => Time.parse(time).localtime,
69
+ :target => target,
70
+ :direction => direction == "<-" ? :from : :to,
71
+ :msg => msg.strip.unpack("M").first}
72
+ end
73
+ ret += @m
74
+ if @m.length < MEMORY_LOG_LENGTH
75
+ @m = (ret[0...n-@m.length] + @m)
76
+ @m = @m[-MEMORY_LOG_LENGTH..-1] if @m.length > MEMORY_LOG_LENGTH
77
+ end
78
+ ret
79
+ end
80
+ end
81
+ end
82
+
83
+ module_function
84
+
85
+ @histories = {}
86
+
87
+ def get(jid)
88
+ name = jid.strip.to_s
89
+ if Config.account[:log]
90
+ path = File.join(Config.account[:log_dir], "#{name}.log")
91
+ @histories[name] ||= History.new(path, name, true)
92
+ else
93
+ @dummy ||= DummyHistory.new
94
+ end
95
+ end
96
+
97
+ def log(msg, direction, target)
98
+ return unless Config.account[:log]
99
+
100
+ init_session_log() unless @session
101
+ @session.log(msg, direction, target)
102
+ end
103
+
104
+ def last(n)
105
+ unless Config.account[:log]
106
+ Out::put("warning: logs are disabled")
107
+ return []
108
+ end
109
+
110
+ init_session_log() unless @session
111
+ @session.last(n)
112
+ end
113
+
114
+ def init_session_log
115
+ return unless Config.account[:log]
116
+
117
+ @session = History.new(path = File.join(Config.account[:log_dir], "session.log"))
118
+ end
119
+ end
120
+
121
+ end
122
+
@@ -0,0 +1,109 @@
1
+
2
+ module Lijab
3
+
4
+ module HooksHandler
5
+ module_function
6
+
7
+ @on_connect = []
8
+ @on_disconnect = []
9
+ @on_incoming_message = []
10
+ @on_presence = []
11
+ @on_pre_send_message = []
12
+ @on_post_send_message = []
13
+
14
+ def init
15
+ @on_connect, @on_disconnect, @on_incoming_message, @on_presence = [], [], [], []
16
+ @on_pre_send_message, @on_post_send_message = [], []
17
+
18
+ Dir[File.join(Config.dirs[:hooks], '**', '*.rb')].each { |f| load f }
19
+
20
+ Main.client.add_message_callback(&method(:handle_message))
21
+ Main.contacts.roster.add_presence_callback(&method(:handle_presence))
22
+ end
23
+
24
+ def handle_message(msg)
25
+ return unless msg.body && !msg.body.empty?
26
+
27
+ @on_incoming_message.each do |b|
28
+ b.call(Main.contacts[msg.from.strip], msg.body)
29
+ end
30
+ end
31
+
32
+ def handle_presence(roster_item, old_p, new_p)
33
+ @on_presence.each do |b|
34
+ b.call(Main.contacts[roster_item.jid.strip], old_p, new_p)
35
+ end
36
+ end
37
+
38
+ def handle_pre_send_message(contact, msg)
39
+ return msg if @on_pre_send_message.empty? || !msg.body || msg.body.empty?
40
+
41
+ @on_pre_send_message.inject(msg) do |ret_msg, block|
42
+ args = [contact, ret_msg.body]
43
+ args.push(msg) if block.arity == 3
44
+
45
+ m = block.call(*args)
46
+ break if !m
47
+
48
+ if m.is_a?(Jabber::Message)
49
+ m
50
+ else
51
+ ret_msg.body = m.to_s
52
+ ret_msg
53
+ end
54
+ end
55
+ end
56
+
57
+ def handle_post_send_message(contact, msg)
58
+ return if !msg.body || msg.body.empty?
59
+
60
+ @on_post_send_message.each do |block|
61
+ args = [contact, msg.body]
62
+ args.push(msg) if block.arity == 3
63
+ block.call(*args)
64
+ end
65
+ end
66
+
67
+ def handle_connect
68
+ @on_connect.each { |b| b.call }
69
+ end
70
+
71
+ def handle_disconnect
72
+ @on_disconnect.each { |b| b.call }
73
+ end
74
+
75
+ attr_reader :on_connect, :on_disconnect, :on_incoming_message, :on_presence,
76
+ :on_pre_send_message, :on_post_send_message
77
+ module_function :on_connect, :on_disconnect, :on_incoming_message,:on_presence,
78
+ :on_pre_send_message, :on_post_send_message
79
+ end
80
+
81
+ module Hooks
82
+ module_function
83
+
84
+ def on_incoming_message(&block)
85
+ HooksHandler::on_incoming_message.push(block)
86
+ end
87
+
88
+ def on_presence(&block)
89
+ HooksHandler::on_presence.push(block)
90
+ end
91
+
92
+ def on_pre_send_message(&block)
93
+ HooksHandler::on_pre_send_message.push(block)
94
+ end
95
+
96
+ def on_post_send_message(&block)
97
+ HooksHandler::on_post_send_message.push(block)
98
+ end
99
+
100
+ def on_connect(&block)
101
+ HooksHandler::on_connect.push(block)
102
+ end
103
+
104
+ def on_disconnect(&block)
105
+ HooksHandler::on_disconnect.push(block)
106
+ end
107
+ end
108
+
109
+ end