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.
- data/bin/lijab +9 -0
- data/ext/extconf.rb +20 -0
- data/ext/readline_extra.c +172 -0
- data/ext/test.rb +29 -0
- data/lib/bleh.rb +45 -0
- data/lib/configs/commands/cowsay.rb +33 -0
- data/lib/configs/hooks/ting.rb +23 -0
- data/lib/lijab.rb +7 -0
- data/lib/lijab/commands.rb +139 -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/config.rb +174 -0
- data/lib/lijab/contacts.rb +324 -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 +248 -0
- data/lib/lijab/out.rb +174 -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/readline/extra.rb +7 -0
- metadata +122 -0
data/lib/lijab/input.rb
ADDED
@@ -0,0 +1,234 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'readline/extra'
|
3
|
+
|
4
|
+
module Lijab
|
5
|
+
|
6
|
+
module InputHandler
|
7
|
+
|
8
|
+
DEFAULT_PROMPT = "> "
|
9
|
+
|
10
|
+
@prompt = DEFAULT_PROMPT
|
11
|
+
@last_to = ""
|
12
|
+
@last_typed = ""
|
13
|
+
@multiline = false
|
14
|
+
@multilines = []
|
15
|
+
|
16
|
+
module_function
|
17
|
+
|
18
|
+
def init
|
19
|
+
Readline::completer_word_break_characters = ""
|
20
|
+
Readline::completion_append_character = " "
|
21
|
+
Readline::completion_proc = method(:completer).to_proc
|
22
|
+
Readline::pre_input_proc = lambda do
|
23
|
+
print "#{ANSI.cleartoeol}" ; STDOUT.flush
|
24
|
+
unless @last_to.empty?
|
25
|
+
Readline::insert_text("#{@last_to}: ")
|
26
|
+
Readline::redisplay
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
if Config.opts[:ctrl_c_quits]
|
31
|
+
trap("SIGINT") { Main.quit }
|
32
|
+
else
|
33
|
+
trap("SIGINT") do
|
34
|
+
Readline::line_buffer = ""
|
35
|
+
puts
|
36
|
+
Out::make_infoline
|
37
|
+
print "#{@prompt}"
|
38
|
+
STDOUT.flush
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
read_typed_history()
|
43
|
+
|
44
|
+
init_char_input_stuff()
|
45
|
+
|
46
|
+
@input_thread = Thread.new { read_input() }
|
47
|
+
end
|
48
|
+
|
49
|
+
def prompt(p=nil)
|
50
|
+
return @prompt unless p
|
51
|
+
@prompt = p
|
52
|
+
end
|
53
|
+
|
54
|
+
def reset_prompt
|
55
|
+
@prompt = DEFAULT_PROMPT
|
56
|
+
end
|
57
|
+
|
58
|
+
def init_char_input_stuff
|
59
|
+
# i'm surprised this doesn't make typing fucking unbearable
|
60
|
+
|
61
|
+
@on_char_input_blocks = []
|
62
|
+
|
63
|
+
@on_char_input_blocks << lambda do |c|
|
64
|
+
to, msg = Readline::line_buffer.split(":", 2).strip
|
65
|
+
if to && msg && Main.contacts.key?(to)
|
66
|
+
# TODO: try to see if a thread improves things
|
67
|
+
Main.contacts[to].typed_stuff
|
68
|
+
end
|
69
|
+
c
|
70
|
+
end
|
71
|
+
|
72
|
+
Readline::char_input_proc = lambda do |c|
|
73
|
+
ret = c
|
74
|
+
@on_char_input_blocks.each do |block|
|
75
|
+
ret = block.call(c)
|
76
|
+
break if ret != c
|
77
|
+
end
|
78
|
+
ret
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_char_input(&block)
|
83
|
+
@on_char_input_blocks << block
|
84
|
+
end
|
85
|
+
|
86
|
+
#def composing_watcher
|
87
|
+
# timer = nil
|
88
|
+
# loop do
|
89
|
+
# sleep(1)
|
90
|
+
|
91
|
+
# buf = Readline::line_buffer
|
92
|
+
# next unless buf != @last_line
|
93
|
+
|
94
|
+
# @last_line = buf
|
95
|
+
# to, msg = buf.split(":", 2).strip
|
96
|
+
|
97
|
+
# next unless to && msg && Main.contacts.key?(to)
|
98
|
+
# end
|
99
|
+
#end
|
100
|
+
|
101
|
+
def read_input
|
102
|
+
loop do
|
103
|
+
Out::make_infoline
|
104
|
+
|
105
|
+
t = Readline::readline(@prompt, true)
|
106
|
+
|
107
|
+
@last_typed = t || ""
|
108
|
+
|
109
|
+
if !t
|
110
|
+
if @multiline
|
111
|
+
@last_typed = @multilines
|
112
|
+
process_input(@multilines.join("\n"))
|
113
|
+
multiline(false)
|
114
|
+
else
|
115
|
+
if Config.opts[:ctrl_c_quits]
|
116
|
+
puts ; next
|
117
|
+
else
|
118
|
+
Main.quit
|
119
|
+
end
|
120
|
+
end
|
121
|
+
elsif !@multiline && t =~ /^\s*$/
|
122
|
+
Readline::HISTORY.pop
|
123
|
+
else
|
124
|
+
Readline::HISTORY.pop if Readline::HISTORY.to_a[-2] == t
|
125
|
+
|
126
|
+
if @multiline
|
127
|
+
@multilines.push(t)
|
128
|
+
@last_typed = @multilines
|
129
|
+
else
|
130
|
+
process_input(t)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def process_input(text)
|
137
|
+
return if text.empty?
|
138
|
+
|
139
|
+
if !Main.connected
|
140
|
+
# FIXME: brute force ftw!
|
141
|
+
Out::error("not connected :-(", false)
|
142
|
+
return
|
143
|
+
end
|
144
|
+
|
145
|
+
if text[0] == ?/
|
146
|
+
Commands::run(*text[1..-1].split(" ", 2))
|
147
|
+
@last_to = ""
|
148
|
+
else
|
149
|
+
to, msg = text.split(":", 2)
|
150
|
+
return unless to && msg && !msg.empty? && Main.contacts.key?(to)
|
151
|
+
msg = msg[1..-1] if msg[0].chr == " " # goddammit, whitespace will be the death of me
|
152
|
+
|
153
|
+
@last_to = to
|
154
|
+
jid = Jabber::JID.new(to)
|
155
|
+
jid = nil unless jid.resource
|
156
|
+
Main.contacts[to].send_message(msg, jid)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def delete_last_typed
|
161
|
+
if @last_typed.is_a?(Array)
|
162
|
+
@last_typed.each do |line|
|
163
|
+
# line length + multiline prompt + \n
|
164
|
+
# FIXME: put the multiline prompt somewhere
|
165
|
+
print "\b" * (line.length + 6)
|
166
|
+
print "#{ANSIMove.up(1)}"
|
167
|
+
end
|
168
|
+
print "#{ANSIMove.down(1)}" if @last_typed.length > 0
|
169
|
+
else
|
170
|
+
print "\b" * @last_typed.length
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def delete_typed
|
175
|
+
if @multiline
|
176
|
+
delete_last_typed()
|
177
|
+
else
|
178
|
+
print "\b" * Readline::line_buffer.length
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def redisplay_input()
|
183
|
+
if @multiline && !@multilines.empty?
|
184
|
+
puts "#{ANSI.clearline}#{DEFAULT_PROMPT}#{@multilines[0]}"
|
185
|
+
@multilines[1..-1].each do |line|
|
186
|
+
puts "#{ANSI.clearline}#{@prompt}#{line}"
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
Out::make_infoline()
|
191
|
+
print "#{@prompt}#{Readline::line_buffer}"
|
192
|
+
STDOUT.flush
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def completer(line)
|
197
|
+
return if !Main.connected
|
198
|
+
|
199
|
+
if line[0] == ?/
|
200
|
+
Commands::completer(line)
|
201
|
+
else
|
202
|
+
Main.contacts.completer(line)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def save_typed_history
|
207
|
+
File.open(Config.account[:typed], 'w') do |f|
|
208
|
+
f.puts(Readline::HISTORY.to_a[-300..-1] || Readline::HISTORY.to_a)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def read_typed_history
|
213
|
+
path = Config.account[:typed]
|
214
|
+
File.read(path).each { |l| Readline::HISTORY.push(l.chomp) } if File.file?(path)
|
215
|
+
end
|
216
|
+
|
217
|
+
def multiline?
|
218
|
+
@multiline
|
219
|
+
end
|
220
|
+
|
221
|
+
def multiline(enable, first_line="")
|
222
|
+
@multiline = enable
|
223
|
+
@multilines = []
|
224
|
+
if enable
|
225
|
+
@multilines.push(first_line) unless first_line.empty?
|
226
|
+
prompt("---> ")
|
227
|
+
else
|
228
|
+
reset_prompt()
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
end
|
234
|
+
|
data/lib/lijab/main.rb
ADDED
@@ -0,0 +1,248 @@
|
|
1
|
+
require 'date'
|
2
|
+
require 'file/tail'
|
3
|
+
require 'monitor'
|
4
|
+
require 'optparse'
|
5
|
+
require 'term/ansicolor'
|
6
|
+
require 'xmpp4r'
|
7
|
+
require 'xmpp4r/roster'
|
8
|
+
require 'yaml'
|
9
|
+
|
10
|
+
require 'lijab/commands'
|
11
|
+
require 'lijab/config'
|
12
|
+
require 'lijab/contacts'
|
13
|
+
require 'lijab/history'
|
14
|
+
require 'lijab/hooks'
|
15
|
+
require 'lijab/input'
|
16
|
+
require 'lijab/out'
|
17
|
+
require 'lijab/version'
|
18
|
+
require 'lijab/xmpp4r/message'
|
19
|
+
|
20
|
+
include Term
|
21
|
+
|
22
|
+
|
23
|
+
class String
|
24
|
+
include ANSIColor
|
25
|
+
|
26
|
+
def colored(*colors)
|
27
|
+
s = self
|
28
|
+
colors.each { |c| s = s.send(c) }
|
29
|
+
s
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Array
|
34
|
+
def strip
|
35
|
+
self.map { |s| s.strip }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
Thread.abort_on_exception = true
|
40
|
+
|
41
|
+
module Lijab
|
42
|
+
|
43
|
+
module Main
|
44
|
+
module_function
|
45
|
+
|
46
|
+
@monitor = Monitor.new
|
47
|
+
|
48
|
+
def run!
|
49
|
+
args = parse_args()
|
50
|
+
Jabber::debug = args[:debug]
|
51
|
+
|
52
|
+
Config::init(args)
|
53
|
+
read_saved_session()
|
54
|
+
|
55
|
+
@connected = false
|
56
|
+
|
57
|
+
print ANSI.title("lijab -- #{Config.jid.strip}") ; STDOUT.flush
|
58
|
+
|
59
|
+
begin
|
60
|
+
setup_client()
|
61
|
+
rescue SystemCallError, SocketError
|
62
|
+
Out::error("couldn't connect", false)
|
63
|
+
reconnect()
|
64
|
+
end
|
65
|
+
|
66
|
+
Commands::init
|
67
|
+
InputHandler::init
|
68
|
+
end
|
69
|
+
|
70
|
+
def setup_after_connect
|
71
|
+
HooksHandler::init
|
72
|
+
end
|
73
|
+
|
74
|
+
def setup_client
|
75
|
+
return unless @monitor.try_enter
|
76
|
+
begin
|
77
|
+
@client = Jabber::Client.new(Config.jid)
|
78
|
+
|
79
|
+
@client.add_message_callback do |msg|
|
80
|
+
if Main.contacts.key?(msg.from)
|
81
|
+
Main.contacts[msg.from].handle_message(msg)
|
82
|
+
else
|
83
|
+
Main.contacts.handle_non_contact_message(msg)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
@client.use_ssl = Config.account[:use_ssl]
|
88
|
+
|
89
|
+
Out::put("connecting...".yellow, false)
|
90
|
+
|
91
|
+
loop do
|
92
|
+
begin
|
93
|
+
@client.connect(Config.account[:server], Config.account[:port])
|
94
|
+
|
95
|
+
if !Config.account[:password]
|
96
|
+
print "#{ANSI.clearline}#{Config.account[:name]} account password: "
|
97
|
+
system("stty -echo") # FIXME
|
98
|
+
STDIN.read_nonblock(9999999) rescue nil
|
99
|
+
Config.account[:password] = gets.chomp
|
100
|
+
system("stty echo")
|
101
|
+
puts
|
102
|
+
end
|
103
|
+
|
104
|
+
@client.auth(Config.account[:password])
|
105
|
+
break
|
106
|
+
rescue Jabber::ClientAuthenticationFailure
|
107
|
+
Out::error("couldn't authenticate: wrong password?", false)
|
108
|
+
Config.account[:password] = nil
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
@client.on_exception do |e,stream,from|
|
113
|
+
@connected = false
|
114
|
+
|
115
|
+
case from
|
116
|
+
when :disconnected
|
117
|
+
Out::error("disconnected", false)
|
118
|
+
HooksHandler::handle_disconnect
|
119
|
+
reconnect()
|
120
|
+
else
|
121
|
+
# death before lost messages!
|
122
|
+
raise e || "exception raised from #{from}"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
@contacts = Contacts::Contacts.new(Jabber::Roster::Helper.new(@client))
|
127
|
+
@client.send(@presence)
|
128
|
+
@connected = true
|
129
|
+
|
130
|
+
setup_after_connect()
|
131
|
+
HooksHandler::handle_connect
|
132
|
+
|
133
|
+
Out::put("connected!".green)
|
134
|
+
ensure
|
135
|
+
@monitor.exit
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def reconnect
|
140
|
+
do_sleep = 1
|
141
|
+
loop do
|
142
|
+
do_sleep.downto(1) do |i|
|
143
|
+
Out::make_infoline
|
144
|
+
Out::infoline("trying reconnect in #{i*5} seconds...")
|
145
|
+
sleep(5)
|
146
|
+
end
|
147
|
+
do_sleep = [do_sleep*2, 10].min
|
148
|
+
|
149
|
+
begin
|
150
|
+
setup_client()
|
151
|
+
Out::clear_infoline
|
152
|
+
break
|
153
|
+
rescue SystemCallError, SocketError
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_status(status, msg=nil)
|
159
|
+
type = status == :invisible ? :unavailable : nil
|
160
|
+
priority = Config.opts[:status_priorities][status]
|
161
|
+
status = nil if [:available, :invisible].include?(status)
|
162
|
+
|
163
|
+
@presence.set_type(type).set_show(status).set_status(msg).set_priority(priority)
|
164
|
+
|
165
|
+
@client.send(@presence)
|
166
|
+
end
|
167
|
+
|
168
|
+
def clear_status_message
|
169
|
+
set_status(@status)
|
170
|
+
end
|
171
|
+
|
172
|
+
def set_priority(priority)
|
173
|
+
@client.send(@presence.set_priority(priority))
|
174
|
+
end
|
175
|
+
|
176
|
+
def parse_args
|
177
|
+
options = {:debug => false}
|
178
|
+
begin
|
179
|
+
op = OptionParser.new do |opts|
|
180
|
+
opts.banner = "usage: lijab [-h | -V | [-a ACCOUNTNAME] [-d BASEDIR] [-D]]\n\n"
|
181
|
+
opts.on("-D", "--[no-]debug",
|
182
|
+
"output xmpp debug information to stderr") { |v| options[:debug] = v }
|
183
|
+
opts.on("-d", "--basedir BASEDIR",
|
184
|
+
"configs base directory") { |v| options[:basedir] = v }
|
185
|
+
opts.on("-a", "--account ACCOUNTNAME",
|
186
|
+
"the name of the account to connect to") { |v| options[:account] = v }
|
187
|
+
opts.on("-V", "--version", "print version information") do |v|
|
188
|
+
puts "lijab #{Lijab::VERSION}"
|
189
|
+
exit(0)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
begin
|
193
|
+
op.parse!
|
194
|
+
rescue OptionParser::ParseError => e
|
195
|
+
puts "#{e}\n\n#{op.banner.chomp}"
|
196
|
+
exit(1)
|
197
|
+
end
|
198
|
+
rescue OptionParser::MissingArgument
|
199
|
+
puts "lijab: error: #{$!}\n\n#{op}"
|
200
|
+
exit 1
|
201
|
+
end
|
202
|
+
options
|
203
|
+
end
|
204
|
+
|
205
|
+
def save_session
|
206
|
+
return unless @presence
|
207
|
+
|
208
|
+
o = {:status => {:type => @presence.type,
|
209
|
+
:show => @presence.show,
|
210
|
+
:status => @presence.status,
|
211
|
+
:priority => @presence.priority}}
|
212
|
+
File.open(File.join(Config.account[:dir], "session_data.yml"), 'w') do |f|
|
213
|
+
f.puts(YAML.dump(o))
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def read_saved_session
|
218
|
+
path = File.join(Config.account[:dir], "session_data.yml")
|
219
|
+
|
220
|
+
if File.file?(path)
|
221
|
+
o = YAML.load_file(path)
|
222
|
+
else
|
223
|
+
o = {:status => {:type => :available, :priority => 51}}
|
224
|
+
end
|
225
|
+
|
226
|
+
@presence = Jabber::Presence.new.set_type(o[:status][:type]) \
|
227
|
+
.set_show(o[:status][:show]) \
|
228
|
+
.set_status(o[:status][:status]) \
|
229
|
+
.set_priority(o[:status][:priority])
|
230
|
+
end
|
231
|
+
|
232
|
+
def quit
|
233
|
+
begin
|
234
|
+
@client.close if @connected
|
235
|
+
rescue
|
236
|
+
end
|
237
|
+
InputHandler::save_typed_history
|
238
|
+
Config::dump_config_file(false, true)
|
239
|
+
save_session()
|
240
|
+
puts "\nexiting..."
|
241
|
+
exit 0
|
242
|
+
end
|
243
|
+
|
244
|
+
attr_reader :contacts, :client, :connected, :presence
|
245
|
+
module_function :contacts, :client, :connected, :presence
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|