termtter 1.7.2 → 1.8.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.
Files changed (81) hide show
  1. data/.gitignore +1 -1
  2. data/README.rdoc +1 -0
  3. data/Rakefile +2 -1
  4. data/VERSION +1 -1
  5. data/bin/termtter +1 -0
  6. data/bin/termtter_frame +134 -0
  7. data/lib/plugins/aa.rb +44 -0
  8. data/lib/plugins/another_prompt.rb +42 -41
  9. data/lib/plugins/appendtitle.rb +82 -0
  10. data/lib/plugins/ar.rb +11 -8
  11. data/lib/plugins/async.rb +3 -2
  12. data/lib/plugins/capital_update.rb +12 -0
  13. data/lib/plugins/channel.rb +149 -0
  14. data/lib/plugins/clock.rb +10 -0
  15. data/lib/plugins/defaults/command_line.rb +8 -0
  16. data/lib/plugins/defaults/confirm.rb +1 -1
  17. data/lib/plugins/defaults/hashtag.rb +1 -1
  18. data/lib/plugins/defaults/keyword.rb +11 -0
  19. data/lib/plugins/defaults/list.rb +32 -6
  20. data/lib/plugins/defaults/standard_commands.rb +135 -52
  21. data/lib/plugins/defaults/standard_completion.rb +14 -0
  22. data/lib/plugins/defaults/stdout.rb +59 -27
  23. data/lib/plugins/defaults/user.rb +32 -0
  24. data/lib/plugins/draft.rb +9 -12
  25. data/lib/plugins/easy_post.rb +5 -0
  26. data/lib/plugins/event_invoked_at.rb +23 -0
  27. data/lib/plugins/expand-tinyurl.rb +13 -20
  28. data/lib/plugins/favotter.rb +9 -9
  29. data/lib/plugins/footer.rb +9 -12
  30. data/lib/plugins/friends.rb +0 -26
  31. data/lib/plugins/grass.rb +2 -4
  32. data/lib/plugins/growl.rb +47 -0
  33. data/lib/plugins/gyazo.rb +16 -18
  34. data/lib/plugins/hi.rb +31 -10
  35. data/lib/plugins/http_server.rb +3 -2
  36. data/lib/plugins/irc_gw.rb +71 -17
  37. data/lib/plugins/line.rb +10 -0
  38. data/lib/plugins/linefeed.rb +6 -1
  39. data/lib/plugins/list_switch.rb +76 -0
  40. data/lib/plugins/nop.rb +9 -0
  41. data/lib/plugins/notify-send.rb +1 -1
  42. data/lib/plugins/notify-send2.rb +25 -16
  43. data/lib/plugins/notify-send3.rb +16 -13
  44. data/lib/plugins/nuance.rb +29 -0
  45. data/lib/plugins/open_url.rb +1 -5
  46. data/lib/plugins/random.rb +2 -6
  47. data/lib/plugins/reply_sound.rb +33 -0
  48. data/lib/plugins/say.rb +2 -2
  49. data/lib/plugins/storage/sqlite3.rb +1 -1
  50. data/lib/plugins/story.rb +44 -0
  51. data/lib/plugins/tinyurl.rb +50 -29
  52. data/lib/plugins/translate_tweet.rb +38 -0
  53. data/lib/plugins/web.rb +27 -0
  54. data/lib/termtter.rb +8 -4
  55. data/lib/termtter/client.rb +17 -21
  56. data/lib/termtter/command.rb +35 -13
  57. data/lib/termtter/config.rb +13 -0
  58. data/lib/termtter/config_template.erb +3 -2
  59. data/lib/termtter/default_config.rb +2 -2
  60. data/lib/termtter/event.rb +69 -0
  61. data/lib/termtter/hook.rb +6 -1
  62. data/lib/termtter/hookable.rb +9 -1
  63. data/lib/termtter/httppool.rb +17 -9
  64. data/lib/termtter/optparse.rb +11 -1
  65. data/lib/termtter/rubytter_proxy.rb +21 -5
  66. data/spec/plugins/capital_update_spec.rb +9 -0
  67. data/spec/plugins/fib_spec.rb +2 -4
  68. data/spec/plugins/hi_spec.rb +9 -0
  69. data/spec/plugins/tinyurl_spec.rb +78 -0
  70. data/spec/termtter/client_spec.rb +5 -12
  71. data/spec/termtter/command_spec.rb +22 -10
  72. data/spec/termtter/config_spec.rb +23 -0
  73. data/spec/termtter/event_spec.rb +129 -0
  74. data/spec/termtter/optparse_spec.rb +2 -2
  75. data/spec/termtter/rubytter_proxy_spec.rb +1 -1
  76. metadata +39 -8
  77. data/bin/kill_termtter +0 -22
  78. data/lib/plugins/defaults/users.rb +0 -63
  79. data/lib/plugins/pause.rb +0 -3
  80. data/test/friends_timeline.json +0 -5
  81. data/test/search.json +0 -8
data/lib/plugins/growl.rb CHANGED
@@ -8,6 +8,53 @@ require 'cgi'
8
8
 
9
9
  begin
10
10
  require 'ruby-growl'
11
+ if RUBY_VERSION >= "1.9" # Fix ruby-growl for multibyte chars if Ruby version is over 1.9
12
+ class Growl
13
+ private
14
+
15
+ def notification_packet(name, title, description, priority, sticky)
16
+ flags = 0
17
+ data = []
18
+
19
+ packet = [
20
+ GROWL_PROTOCOL_VERSION,
21
+ GROWL_TYPE_NOTIFICATION,
22
+ ]
23
+
24
+ flags = 0
25
+ flags |= ((0x7 & priority) << 1) # 3 bits for priority
26
+ flags |= 1 if sticky # 1 bit for sticky
27
+
28
+ packet << flags
29
+ packet << name.bytesize
30
+ packet << title.bytesize
31
+ packet << description.bytesize
32
+ packet << @app_name.bytesize
33
+
34
+ data << name
35
+ data << title
36
+ data << description
37
+ data << @app_name
38
+
39
+ packet << data.join
40
+ packet = packet.pack GNN_FORMAT
41
+
42
+ checksum = MD5.new packet
43
+ checksum.update @password unless @password.nil?
44
+
45
+ packet << checksum.digest
46
+
47
+ return packet.force_encoding('utf-8')
48
+ end
49
+
50
+ def send(packet)
51
+ set_sndbuf packet.bytesize
52
+ @socket.send packet, 0
53
+ @socket.flush
54
+ end
55
+ end
56
+ end
57
+
11
58
  growl = Growl.new "localhost", "termtter", ["update_friends_timeline"]
12
59
  rescue LoadError
13
60
  growl = nil
data/lib/plugins/gyazo.rb CHANGED
@@ -3,26 +3,26 @@ require 'net/http'
3
3
  def post_gyazo
4
4
  browser_cmd = 'firefox'
5
5
  gyazo_url = ""
6
-
6
+
7
7
  idfile = ENV['HOME'] + "/.gyazo.id"
8
-
9
- id = ''
10
- if File.exist?(idfile) then
8
+
9
+ id = nil
10
+ if File.exist?(idfile)
11
11
  id = File.read(idfile).chomp
12
12
  else
13
13
  id = Time.new.strftime("%Y%m%d%H%M%S")
14
14
  File.open(idfile,"w").print(id+"\n")
15
15
  end
16
-
16
+
17
17
  tmpfile = "/tmp/image_upload#{$$}.png"
18
-
19
- system "import #{tmpfile}"
20
-
18
+
19
+ system import, tmpfile
20
+
21
21
  imagedata = File.read(tmpfile)
22
22
  File.delete(tmpfile)
23
-
23
+
24
24
  boundary = '----BOUNDARYBOUNDARY----'
25
-
25
+
26
26
  data = <<EOF
27
27
  --#{boundary}\r
28
28
  content-disposition: form-data; name="id"\r
@@ -36,19 +36,19 @@ content-disposition: form-data; name="imagedata"\r
36
36
  --#{boundary}--\r
37
37
  EOF
38
38
 
39
- header ={
39
+ header = {
40
40
  'Content-Length' => data.length.to_s,
41
41
  'Content-type' => "multipart/form-data; boundary=#{boundary}"
42
42
  }
43
43
 
44
- Net::HTTP.start("gyazo.com",80){|http|
45
- res = http.post("/upload.cgi",data,header)
44
+ Net::HTTP.start("gyazo.com", 80){|http|
45
+ res = http.post("/upload.cgi", data, header)
46
46
  url = res.response.to_ary[1]
47
47
  puts url
48
48
  system "#{browser_cmd} #{url}"
49
49
  gyazo_url = url
50
50
  }
51
- return gyazo_url
51
+ gyazo_url
52
52
  end
53
53
 
54
54
  Termtter::Client.register_command(
@@ -63,10 +63,9 @@ Termtter::Client.register_command(
63
63
  )
64
64
 
65
65
  # gyazo:
66
- # Capture an arbitary desktop area and upload the image to gyazo.com,
66
+ # Capture an arbitary desktop area and upload the image to gyazo.com,
67
67
  # open it up with the browser you specified. Then, tweet the link with
68
- # a message.
69
- #
68
+ # a message.
70
69
  # You need ImageMagick tools and a web browser(default:Firefox, as you
71
70
  # can see.)
72
71
  #
@@ -75,4 +74,3 @@ Termtter::Client.register_command(
75
74
  #
76
75
  # example:
77
76
  # gyazo What a lame dialogue message! (capture process starts...)
78
- #
data/lib/plugins/hi.rb CHANGED
@@ -1,15 +1,36 @@
1
1
  # -*- coding: utf-8 -*-
2
+ # あいうえお
2
3
  module Termtter::Client
3
- [:hi, :hola].each do |hi|
4
- register_command(hi, :help => ["#{hi} [(Optinal) USER]", "Post a #{hi}"]) do |arg|
5
- result =
6
- if arg.empty?
7
- Termtter::API.twitter.update(hi.to_s)
8
- else
9
- name = normalize_as_user_name(arg)
10
- Termtter::API.twitter.update("#{hi} @#{name}")
11
- end
12
- puts "=> " << result.text
4
+ {
5
+ :english => ['hi', 'hey', 'hello', 'How are you?', "How's going?"],
6
+ :spanish => ['¡Hola!', '¿Cómo estás?'],
7
+ :german => ['Guten Tag!'],
8
+ }.each do |language, greetings|
9
+ greetings.each do |greeting|
10
+ # '¿Cómo estás?' -> 'como_estas'
11
+ # MEMO:
12
+ # command_name = greeting.tr('áó', 'ao').scan(/\w+/).join('_').downcase
13
+ # works only on ruby 1.9
14
+ command_name = greeting.
15
+ gsub('á', 'a').
16
+ gsub('ó', 'o').
17
+ scan(/[a-zA-Z]+/).
18
+ join('_').
19
+ downcase
20
+ register_command(
21
+ :name => command_name,
22
+ :author => 'ujihisa',
23
+ :help => ["#{command_name} [(Optinal) USER]", "Post a greeting message in #{language.to_s.capitalize}"],
24
+ :exec_proc => lambda {|arg|
25
+ result =
26
+ if arg.empty?
27
+ Termtter::API.twitter.update(greeting)
28
+ else
29
+ name = normalize_as_user_name(arg)
30
+ Termtter::API.twitter.update("@#{name} #{greeting}")
31
+ end
32
+ puts "=> " << result.text
33
+ })
13
34
  end
14
35
  end
15
36
  end
@@ -47,7 +47,7 @@ module Termtter::Client
47
47
 
48
48
  @http_server.mount_proc('/reload.html') do |req, res|
49
49
  # MEMO: ブラウザで画面を二つ開いてるとデータの取り合いになっておかしな感じになる。。。
50
- res['Content-Type'] = 'text/javascript; charset=utf-8';
50
+ res['Content-Type'] = 'text/javascript; charset=utf-8'
51
51
  res.body = @http_server_statuses_store.to_json
52
52
  @http_server_statuses_store.clear
53
53
  end
@@ -67,7 +67,7 @@ module Termtter::Client
67
67
  begin
68
68
  command = req.path.sub(/^\//, '')
69
69
  execute(command)
70
- res['Content-Type'] = 'text/javascript; charset=utf-8';
70
+ res['Content-Type'] = 'text/javascript; charset=utf-8'
71
71
  res.body = @http_server_output
72
72
  rescue Termtter::CommandNotFound => e
73
73
  res.status = 404
@@ -80,3 +80,4 @@ module Termtter::Client
80
80
  @http_server.start
81
81
  end
82
82
  end
83
+ # vim: fileencoding=utf8
@@ -1,10 +1,14 @@
1
1
  # -*- coding: utf-8 -*-
2
2
 
3
3
  require 'net/irc'
4
+ require 'set'
4
5
 
5
6
  config.plugins.irc_gw.set_default(:port, 16669)
6
7
  config.plugins.irc_gw.set_default(:last_statuses_count, 100)
7
8
  config.plugins.irc_gw.set_default(:logger_level, Logger::ERROR)
9
+ config.plugins.irc_gw.set_default(:command_regexps, [/^(.+): *(.*)/])
10
+
11
+ Termtter::Client.plug 'multi_output'
8
12
 
9
13
  module Termtter::Client
10
14
  class << self
@@ -15,12 +19,12 @@ module Termtter::Client
15
19
  begin
16
20
  puts "collecting friends (#{frinends.length})"
17
21
  last = Termtter::API::twitter.friends(user_name, :cursor => last ? last.next_cursor : -1)
18
- frinends += last.users
22
+ frinends += last.users
19
23
  rescue Timeout::Error, StandardError # XXX
20
24
  break
21
25
  end until last.next_cursor == 0
22
26
  puts "You have #{frinends.length} friends."
23
- frinends.map(&:screen_name)
27
+ Set.new(frinends.map(&:screen_name))
24
28
  end
25
29
  end
26
30
  end
@@ -58,17 +62,40 @@ class TermtterIrcGateway < Net::IRC::Server::Session
58
62
  def initialize(*args)
59
63
  super
60
64
  @@listners << self
65
+ @friends = Set.new
66
+ @commands = []
67
+
68
+ Termtter::Client.register_hook(:collect_user_names_for_irc_gw, :point => :pre_filter) do |statuses, event|
69
+ new_users = []
70
+ statuses.each do |s|
71
+ screen_name = s.user.screen_name
72
+ next if screen_name == @user # XXX
73
+ next if @friends.include? screen_name
74
+ @friends << screen_name
75
+ new_users << screen_name
76
+ end
77
+ join_members(new_users)
78
+ end
79
+
80
+ Termtter::Client.register_command(
81
+ :name => :collect_friends,
82
+ :help => 'Collect friends for IRC.',
83
+ :exec => lambda {|arg|
84
+ sync_friends
85
+ })
86
+
87
+ Termtter::Client.register_hook(:collect_commands_for_irc_gw, :point => :post_command) do |text|
88
+ sync_commands if text =~ /plug/
89
+ end
61
90
  end
62
91
 
63
92
  def call(statuses, event)
64
- msg_type =
65
- case event
66
- when :update_friends_timeline
67
- PRIVMSG
68
- else
69
- time_format = Termtter::Client.time_format_for statuses
70
- NOTICE
71
- end
93
+ if event == :update_friends_timeline
94
+ msg_type = PRIVMSG
95
+ else
96
+ time_format = Termtter::Client.time_format_for statuses
97
+ msg_type = NOTICE
98
+ end
72
99
  statuses.each do |s|
73
100
  typable_id = Termtter::Client.data_to_typable_id(s.id)
74
101
  time = Time.parse(s.created_at).strftime(time_format) if time_format
@@ -88,9 +115,10 @@ class TermtterIrcGateway < Net::IRC::Server::Session
88
115
 
89
116
  def on_user(m)
90
117
  super
118
+ @user = m.params.first
91
119
  post @prefix, JOIN, main_channel
92
120
  post server_name, MODE, main_channel, "+o", @prefix.nick
93
- check_friends
121
+ sync_commands
94
122
  self.call(@@last_statuses || [], :update_friends_timeline)
95
123
  end
96
124
 
@@ -101,10 +129,20 @@ class TermtterIrcGateway < Net::IRC::Server::Session
101
129
  return unless Termtter::Client.find_command(termtter_command)
102
130
  post '#termtter', NOTICE, main_channel, '> ' + termtter_command
103
131
  Termtter::Client.execute(termtter_command)
104
- else
105
- Termtter::Client.execute('update ' + message)
106
- post @prefix, TOPIC, main_channel, message
132
+ return
133
+ end
134
+ config.plugins.irc_gw.command_regexps and
135
+ config.plugins.irc_gw.command_regexps.each do |rule|
136
+ if message =~ rule
137
+ command = message.scan(rule).first.join(' ')
138
+ next unless Termtter::Client.find_command(command)
139
+ post '#termtter', NOTICE, main_channel, '> ' + command
140
+ Termtter::Client.execute(command)
141
+ return
142
+ end
107
143
  end
144
+ Termtter::Client.execute('update ' + message)
145
+ post @prefix, TOPIC, main_channel, message
108
146
  rescue Exception => e
109
147
  post '#termtter', NOTICE, main_channel, "#{e.class.to_s}: #{e.message}"
110
148
  Termtter::Client.handle_error(e)
@@ -116,11 +154,26 @@ class TermtterIrcGateway < Net::IRC::Server::Session
116
154
  end
117
155
  end
118
156
 
119
- def check_friends
157
+ def sync_friends
158
+ previous_friends = @friends
159
+ new_friends = Termtter::Client.following_friends
160
+ diff = new_friends - previous_friends
161
+ join_members(diff)
162
+ @friends += diff
163
+ end
164
+
165
+ def sync_commands
166
+ previous_commands = @commands
167
+ new_commands = Termtter::Client.commands.keys.map{|s| s.to_s.split(' ')}.flatten.uniq
168
+ join_members(new_commands - previous_commands)
169
+ @commands = new_commands
170
+ end
171
+
172
+ def join_members(members)
120
173
  params = []
121
174
  max_params_count = 3
122
- Termtter::Client.following_friends.each do |name|
123
- prefix = Prefix.new("#{name}!#{name}@localhost")
175
+ members.each do |member|
176
+ prefix = Prefix.new("#{member}!#{member}@localhost")
124
177
  post prefix, JOIN, main_channel
125
178
  params << prefix.nick
126
179
  next if params.size < max_params_count
@@ -130,6 +183,7 @@ class TermtterIrcGateway < Net::IRC::Server::Session
130
183
  end
131
184
  post server_name, MODE, main_channel, "+#{"v" * params.size}", *params unless params.empty?
132
185
  end
186
+
133
187
  end
134
188
 
135
189
  unless defined? IRC_SERVER
@@ -0,0 +1,10 @@
1
+ Termtter::Client.register_command(
2
+ :name => :line,
3
+ :help => 'Draws a line without any side effect on Twitter.',
4
+ #:aliases => [:l],
5
+ :exec_proc => lambda {|arg|
6
+ window_width = (ENV['COLUMNS'] || `stty size`[/\d+\s+(\d+)/, 1]).to_i
7
+ text = '<dark>' + (' ' * window_width) + '</dark>'
8
+ puts TermColor.parse(text)
9
+ }
10
+ )
@@ -17,9 +17,14 @@ Termtter::Client.register_hook(
17
17
  cs = line.unpack 'U*'
18
18
  while cs.size > 0
19
19
  sz = 0
20
- l = cs.take_while{|c| sz += c < 0x100 ? 1 : 2; sz < width}
20
+ l = cs.take_while do |c|
21
+ sz2 = c < 0x100 ? 1 : 2
22
+ sz += sz2
23
+ sz < width || (sz2 == 1 && c != 32)
24
+ end
21
25
  ocs += l
22
26
  cs = cs.drop(l.size)
27
+ cs = cs.drop(1) if cs[0] == 32
23
28
  ocs += [0x0a]
24
29
  end
25
30
  end
@@ -0,0 +1,76 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter
4
+ module Client
5
+ register_command('list switch',
6
+ :help => ["list switch LISTNAME", "Switch to the list"]
7
+ ) do |arg|
8
+ @since_id = nil
9
+ Termtter::Plugins::ListSwitch.list_switch(arg)
10
+ end
11
+
12
+ # TODO: コマンド名が微妙...
13
+ register_command('list switch restore',
14
+ :help => ["list switch restore LISTNAME", "Restore to switch list "]
15
+ ) do |arg|
16
+ Termtter::Plugins::ListSwitch.restore
17
+ end
18
+ end
19
+
20
+ class RubytterProxy
21
+ alias_method :call_rubytter_without_list_switch, :call_rubytter
22
+ def call_rubytter_with_list_switch(method, *args, &block)
23
+ Termtter::Plugins::ListSwitch.call_rubytter(self, method, *args, &block)
24
+ end
25
+ alias_method :call_rubytter, :call_rubytter_with_list_switch
26
+ end
27
+
28
+ module Plugins
29
+ module ListSwitch
30
+ extend self
31
+
32
+ attr_reader :active, :list_name, :list_user_name, :list_user_id
33
+
34
+ def call_rubytter(rubytter_proxy, method, *args, &block)
35
+ if active
36
+ case method
37
+ when :home_timeline
38
+ # => list_statuses(user_name, slug, options)
39
+ method, args = :list_statuses, [list_user_name, list_name, *args]
40
+ when :follow
41
+ # => add_member_to_list(slug, user.id)
42
+ method, args = :add_member_to_list, [list_name, *args]
43
+ when :leave
44
+ # => remove_member_from_list(slug, user.id)
45
+ method, args = :remove_member_from_list, [list_name, *args]
46
+ end
47
+ end
48
+ rubytter_proxy.call_rubytter_without_list_switch(method, *args, &block)
49
+ end
50
+
51
+ def list_switch(full_name)
52
+ @active = true
53
+ @list_user_name, @list_name = split_list_name(full_name)
54
+ user = Termtter::API.twitter.cached_user(list_user_name) ||
55
+ Termtter::API.twitter.user(list_user_name)
56
+ @list_user_id = user.id
57
+ # TODO: やっつけなのでちゃんとやる
58
+ config.prompt = full_name + '> '
59
+ end
60
+
61
+ def restore
62
+ @active = false
63
+ # TODO: やっつけなのでちゃんとやる
64
+ config.prompt = '> '
65
+ end
66
+
67
+ def split_list_name(list_name)
68
+ if /([^\/]+)\/([^\/]+)/ =~ list_name
69
+ [Termtter::Client.normalize_as_user_name($1), $2]
70
+ else
71
+ [config.user_name, $2]
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end