lijab 0.1.1

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.
@@ -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