draftcode-termtter 1.0.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (118) hide show
  1. data/History.txt +4 -0
  2. data/README.rdoc +100 -0
  3. data/Rakefile +55 -0
  4. data/bin/kill_termtter +22 -0
  5. data/bin/termtter +6 -0
  6. data/lib/plugins/addspace.rb +27 -0
  7. data/lib/plugins/april_fool.rb +15 -0
  8. data/lib/plugins/bomb.rb +39 -0
  9. data/lib/plugins/clear.rb +14 -0
  10. data/lib/plugins/command_plus.rb +47 -0
  11. data/lib/plugins/confirm.rb +33 -0
  12. data/lib/plugins/cool.rb +10 -0
  13. data/lib/plugins/countter.rb +23 -0
  14. data/lib/plugins/devel.rb +18 -0
  15. data/lib/plugins/direct_messages.rb +36 -0
  16. data/lib/plugins/en2ja.rb +11 -0
  17. data/lib/plugins/english.rb +25 -0
  18. data/lib/plugins/erb.rb +17 -0
  19. data/lib/plugins/exec.rb +17 -0
  20. data/lib/plugins/exec_and_update.rb +14 -0
  21. data/lib/plugins/expand-tinyurl.rb +34 -0
  22. data/lib/plugins/fib.rb +28 -0
  23. data/lib/plugins/fib_filter.rb +14 -0
  24. data/lib/plugins/filter.rb +69 -0
  25. data/lib/plugins/graduatter.rb +8 -0
  26. data/lib/plugins/grass.rb +27 -0
  27. data/lib/plugins/group.rb +64 -0
  28. data/lib/plugins/growl.rb +52 -0
  29. data/lib/plugins/growl2.rb +143 -0
  30. data/lib/plugins/hatebu.rb +59 -0
  31. data/lib/plugins/hatebu_and_update.rb +58 -0
  32. data/lib/plugins/history.rb +93 -0
  33. data/lib/plugins/ignore.rb +19 -0
  34. data/lib/plugins/keyword.rb +18 -0
  35. data/lib/plugins/l2.rb +25 -0
  36. data/lib/plugins/log.rb +73 -0
  37. data/lib/plugins/me.rb +10 -0
  38. data/lib/plugins/modify_arg_hook_sample.rb +7 -0
  39. data/lib/plugins/msagent.rb +38 -0
  40. data/lib/plugins/multi_reply.rb +27 -0
  41. data/lib/plugins/notify-send.rb +22 -0
  42. data/lib/plugins/notify-send2.rb +52 -0
  43. data/lib/plugins/notify-send3.rb +45 -0
  44. data/lib/plugins/open_url.rb +59 -0
  45. data/lib/plugins/otsune.rb +21 -0
  46. data/lib/plugins/outputz.rb +33 -0
  47. data/lib/plugins/pause.rb +3 -0
  48. data/lib/plugins/post_exec_hook_sample.rb +9 -0
  49. data/lib/plugins/pre_exec_hook_sample.rb +9 -0
  50. data/lib/plugins/primes.rb +30 -0
  51. data/lib/plugins/quicklook.rb +41 -0
  52. data/lib/plugins/random.rb +23 -0
  53. data/lib/plugins/reblog.rb +40 -0
  54. data/lib/plugins/reload.rb +3 -0
  55. data/lib/plugins/reply.rb +8 -0
  56. data/lib/plugins/retweet.rb +46 -0
  57. data/lib/plugins/reverse.rb +13 -0
  58. data/lib/plugins/say.rb +26 -0
  59. data/lib/plugins/scrape.rb +41 -0
  60. data/lib/plugins/screen-notify.rb +20 -0
  61. data/lib/plugins/screen.rb +24 -0
  62. data/lib/plugins/shell.rb +14 -0
  63. data/lib/plugins/sl.rb +48 -0
  64. data/lib/plugins/spam.rb +16 -0
  65. data/lib/plugins/standard_plugins.rb +525 -0
  66. data/lib/plugins/stdout.rb +220 -0
  67. data/lib/plugins/storage.rb +55 -0
  68. data/lib/plugins/storage/DB.rb +37 -0
  69. data/lib/plugins/storage/status.rb +83 -0
  70. data/lib/plugins/storage/status_mook.rb +30 -0
  71. data/lib/plugins/switch_user.rb +22 -0
  72. data/lib/plugins/system_status.rb +33 -0
  73. data/lib/plugins/timer.rb +18 -0
  74. data/lib/plugins/tinyurl.rb +20 -0
  75. data/lib/plugins/translation.rb +38 -0
  76. data/lib/plugins/typable_id.rb +94 -0
  77. data/lib/plugins/update_editor.rb +53 -0
  78. data/lib/plugins/uri-open.rb +66 -0
  79. data/lib/plugins/wassr_post.rb +22 -0
  80. data/lib/plugins/yhara.rb +148 -0
  81. data/lib/plugins/yhara_filter.rb +8 -0
  82. data/lib/plugins/yonda.rb +21 -0
  83. data/lib/termtter.rb +38 -0
  84. data/lib/termtter/api.rb +57 -0
  85. data/lib/termtter/client.rb +324 -0
  86. data/lib/termtter/command.rb +80 -0
  87. data/lib/termtter/config.rb +68 -0
  88. data/lib/termtter/config_setup.rb +38 -0
  89. data/lib/termtter/connection.rb +41 -0
  90. data/lib/termtter/hook.rb +31 -0
  91. data/lib/termtter/optparse.rb +14 -0
  92. data/lib/termtter/system_extensions.rb +115 -0
  93. data/lib/termtter/task.rb +17 -0
  94. data/lib/termtter/task_manager.rb +116 -0
  95. data/lib/termtter/version.rb +4 -0
  96. data/spec/plugins/cool_spec.rb +10 -0
  97. data/spec/plugins/english_spec.rb +19 -0
  98. data/spec/plugins/fib_spec.rb +15 -0
  99. data/spec/plugins/filter_spec.rb +18 -0
  100. data/spec/plugins/pause_spec.rb +8 -0
  101. data/spec/plugins/primes_spec.rb +15 -0
  102. data/spec/plugins/shell_spec.rb +10 -0
  103. data/spec/plugins/sl_spec.rb +8 -0
  104. data/spec/plugins/spam_spec.rb +4 -0
  105. data/spec/plugins/standard_plugins_spec.rb +23 -0
  106. data/spec/plugins/storage/DB_spec.rb +12 -0
  107. data/spec/plugins/storage/status_spec.rb +24 -0
  108. data/spec/spec_helper.rb +7 -0
  109. data/spec/termtter/client_spec.rb +255 -0
  110. data/spec/termtter/command_spec.rb +107 -0
  111. data/spec/termtter/config_spec.rb +111 -0
  112. data/spec/termtter/hook_spec.rb +78 -0
  113. data/spec/termtter/task_manager_spec.rb +78 -0
  114. data/spec/termtter/task_spec.rb +22 -0
  115. data/spec/termtter_spec.rb +29 -0
  116. data/test/friends_timeline.json +5 -0
  117. data/test/search.json +8 -0
  118. metadata +211 -0
@@ -0,0 +1,220 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'termcolor'
4
+ require 'erb'
5
+ require 'tempfile'
6
+ require 'curses'
7
+ require 'dl/import'
8
+
9
+ module Readline
10
+ begin
11
+ module LIBREADLINE
12
+ if DL.const_defined? :Importable
13
+ extend DL::Importable
14
+ else
15
+ extend DL::Importer
16
+ end
17
+ pathes = Array(ENV['TERMTTER_EXT_LIB'] || [
18
+ '/opt/local/lib/libreadline.dylib',
19
+ '/usr/lib/libreadline.so',
20
+ '/usr/local/lib/libreadline.so',
21
+ File.join(Gem.bindir, 'readline.dll')
22
+ ])
23
+ dlload(pathes.find { |path| File.exist?(path)})
24
+ extern 'int rl_refresh_line(int, int)'
25
+ end
26
+ def self.refresh_line
27
+ LIBREADLINE.rl_refresh_line(0, 0)
28
+ end
29
+ rescue Exception
30
+ def self.refresh_line;end
31
+ end
32
+ end
33
+
34
+ if win?
35
+ require 'iconv'
36
+
37
+ module Readline
38
+ $iconv_sj_to_u8 = Iconv.new('UTF-8', "CP#{$wGetACP.call()}")
39
+ alias :old_readline :readline
40
+ def readline(*a)
41
+ str = old_readline(*a)
42
+ out = ''
43
+ loop do
44
+ begin
45
+ out << $iconv_sj_to_u8.iconv(str)
46
+ break
47
+ rescue Iconv::Failure
48
+ out << "#{$!.success}?"
49
+ str = $!.failed[1..-1]
50
+ end
51
+ end
52
+ return out
53
+ end
54
+ module_function :old_readline, :readline
55
+ end
56
+ end
57
+
58
+ config.plugins.stdout.set_default(
59
+ :colors,
60
+ [:none, :red, :green, :yellow, :blue, :magenta, :cyan])
61
+ config.plugins.stdout.set_default(
62
+ :timeline_format,
63
+ '<90><%=time%></90> <<%=status_color%>><%=status%></<%=status_color%>> <90><%=id%></90>')
64
+ config.plugins.stdout.set_default(:search_highlight_format, '<on_magenta><white>\1</white></on_magenta>')
65
+
66
+ config.plugins.stdout.set_default(:enable_pager, true)
67
+ config.plugins.stdout.set_default(:pager, 'less -R -f +G')
68
+ config.plugins.stdout.set_default(:window_height, 50)
69
+
70
+ class String
71
+ def truncate_column(col)
72
+ count = 0
73
+ ary = []
74
+ ret = []
75
+ self.split(//u).each {|c|
76
+ count += (c.size == 1 ? 1 : 2)
77
+ if count > col then
78
+ ret.push(ary.to_s)
79
+ ary = []
80
+ count = 0
81
+ end
82
+ ary.push(c)
83
+ }
84
+ ret.push(ary.to_s) unless ary.empty?
85
+ ret
86
+ end
87
+ end
88
+
89
+ module Termtter
90
+ class StdOut < Hook
91
+ def initialize
92
+ super(:name => :stdout, :points => [:output])
93
+ @input_thread = nil
94
+ Client.register_hook(
95
+ :name => :stdout_exit,
96
+ :points => [:exit],
97
+ :exec_proc => lambda { @input_thread.kill if @input_thread }
98
+ )
99
+ Client.register_hook(
100
+ :name => :stdout_readline_yield_thread,
101
+ :points => [:before_task_thread_run],
102
+ :exec_proc => lambda { start_input_thread() unless @input_thread }
103
+ )
104
+ end
105
+
106
+ def call(statuses, event)
107
+ print_statuses(statuses)
108
+ end
109
+
110
+ def trap_setting()
111
+ begin
112
+ stty_save = `stty -g`.chomp
113
+ trap("INT") do
114
+ begin
115
+ system "stty", stty_save
116
+ ensure
117
+ exit
118
+ end
119
+ end
120
+ rescue Errno::ENOENT
121
+ end
122
+ end
123
+
124
+ def setup_readline
125
+ if Readline.respond_to?(:basic_word_break_characters=)
126
+ Readline.basic_word_break_characters= "\t\n\"\\'`><=;|&{("
127
+ end
128
+ Readline.completion_proc = Client.get_command_completion_proc()
129
+ vi_or_emacs = config.editing_mod
130
+ unless vi_or_emacs.empty?
131
+ Readline.__send__("#{vi_or_emacs}_editing_mode")
132
+ end
133
+ end
134
+
135
+ def start_input_thread
136
+ setup_readline()
137
+ trap_setting()
138
+ @input_thread = Thread.new do
139
+ while buf = Readline.readline(ERB.new(config.prompt).result(API.twitter.__send__(:binding)), true)
140
+ Readline::HISTORY.pop if buf.empty?
141
+ begin
142
+ Client.call_commands(buf)
143
+ rescue CommandNotFound => e
144
+ warn "Unknown command \"#{e}\""
145
+ warn 'Enter "help" for instructions'
146
+ rescue => e
147
+ Client.handle_error e
148
+ end
149
+ end
150
+ end
151
+ @input_thread.join
152
+ end
153
+
154
+ def print_statuses(statuses, sort = true, time_format = nil)
155
+ return unless statuses and statuses.first
156
+ unless time_format
157
+ t0 = Time.now
158
+ t1 = Time.parse(statuses.first[:created_at])
159
+ t2 = Time.parse(statuses.last[:created_at])
160
+ time_format =
161
+ if [t0.year, t0.month, t0.day] == [t1.year, t1.month, t1.day] \
162
+ and [t1.year, t1.month, t1.day] == [t2.year, t2.month, t2.day]
163
+ '%H:%M:%S'
164
+ else
165
+ '%y/%m/%d %H:%M'
166
+ end
167
+ end
168
+
169
+ output_text = ''
170
+ output_text += "\e[1K\e[0G" unless win?
171
+ Curses::init_screen;
172
+ cols = Curses.cols;
173
+ Curses::close_screen
174
+ statuses.each do |s|
175
+ text = s.text
176
+ status_color = config.plugins.stdout.colors[s.user.id.hash % config.plugins.stdout.colors.size]
177
+ status = "#{s.user.screen_name}: #{TermColor.escape(text)}"
178
+ if s.in_reply_to_status_id
179
+ status += " (reply to #{s.in_reply_to_status_id})"
180
+ end
181
+
182
+ time = "(#{Time.parse(s.created_at).strftime(time_format)})"
183
+ id = s.id
184
+ len = id.to_s.length + 2
185
+ if cols > 0 then
186
+ status = status.truncate_column(cols-len-1).join( "\n" )
187
+ status = status.gsub( /\n/, "\n".ljust(len) )
188
+ end
189
+ source =
190
+ case s.source
191
+ when />(.*?)</ then $1
192
+ when 'web' then 'web'
193
+ end
194
+
195
+ erbed_text = ERB.new(config.plugins.stdout.timeline_format).result(binding)
196
+ output_text << TermColor.parse(erbed_text) + "\n"
197
+ end
198
+
199
+ if config.plugins.stdout.enable_pager && ENV['LINES'] && statuses.size > ENV['LINES'].to_i
200
+ file = Tempfile.new('termtter')
201
+ file.print output_text
202
+ file.close
203
+ system "#{config.plugins.stdout.pager} #{file.path}"
204
+ file.close(true)
205
+ else
206
+ output_text << TermColor.parse("<90>-----Fetched on " + Time.now.strftime(time_format) + "</90>\n")
207
+ print output_text
208
+ end
209
+ Readline.refresh_line
210
+ end
211
+ end
212
+
213
+ Client.register_hook(StdOut.new)
214
+ end
215
+
216
+ # stdout.rb
217
+ # output statuses to stdout
218
+ # example config
219
+ # config.plugins.stdout.colors = [:none, :red, :green, :yellow, :blue, :magenta, :cyan]
220
+ # config.plugins.stdout.timeline_format = '<90><%=time%></90> <<%=status_color%>><%=status%></<%=status_color%>> <90><%=id%></90>'
@@ -0,0 +1,55 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'pp'
4
+ require 'time'
5
+
6
+ require File.dirname(__FILE__) + '/storage/status'
7
+
8
+ module Termtter::Client
9
+ public_storage[:log] = []
10
+
11
+ register_hook(
12
+ :name => :storage,
13
+ :points => [:pre_filter],
14
+ :exec_proc => lambda {|statuses, event|
15
+ statuses.each do |s|
16
+ Termtter::Storage::Status.insert(
17
+ :post_id => s.id,
18
+ :created_at => Time.parse(s.created_at).to_i,
19
+ :in_reply_to_status_id => s.in_reply_to_status_id,
20
+ :in_reply_to_user_id => s.in_reply_to_user_id,
21
+ :text => s.text,
22
+ :user_id => s.user.id,
23
+ :screen_name => s.user.screen_name
24
+ )
25
+ end
26
+ }
27
+ )
28
+
29
+ register_command(
30
+ :name => :search_storage,
31
+ :aliases => [:ss],
32
+ :exec_proc => lambda {|arg|
33
+ unless arg.strip.empty?
34
+ key = arg.strip
35
+ statuses = Termtter::Storage::Status.search({:text => key})
36
+ output(statuses, :search)
37
+ end
38
+ },
39
+ :help => [ 'search_storage WORD', 'Search storage for WORD' ]
40
+ )
41
+
42
+ register_command(
43
+ :name => :search_storage_user,
44
+ :aliases => [:ssu],
45
+ :exec_proc => lambda {|arg|
46
+ unless arg.strip.empty?
47
+ key = arg.strip
48
+ statuses = Termtter::Storage::Status.search_user({:user => key})
49
+ output(statuses, :search)
50
+ end
51
+ },
52
+ :help => [ 'search_storage_user SCREEN_NAME', 'Search storage for SCREE_NAME' ]
53
+ )
54
+
55
+ end
@@ -0,0 +1,37 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sqlite3'
4
+ require 'singleton'
5
+
6
+ module Termtter::Storage
7
+ class DB
8
+ include Singleton
9
+ attr_reader :db
10
+
11
+ def initialize
12
+ @db = SQLite3::Database.new(Termtter::CONF_DIR + '/storage.db')
13
+ @db.type_translation = true
14
+ create_table
15
+ end
16
+
17
+ def create_table
18
+ sql =<<-SQL
19
+ CREATE TABLE IF NOT EXISTS user (
20
+ id int NOT NULL,
21
+ screen_name text,
22
+ PRIMARY KEY (id)
23
+ );
24
+ CREATE TABLE IF NOT EXISTS post (
25
+ post_id int NOT NULL, -- twitter側のpostのid
26
+ created_at int, -- 日付(RubyでUNIX時間に変換)
27
+ in_reply_to_status_id int, -- あったほうがよいらしい
28
+ in_reply_to_user_id int, -- あったほうがよいらしい
29
+ post_text text,
30
+ user_id int NOT NULL,
31
+ PRIMARY KEY (post_id)
32
+ );
33
+ SQL
34
+ @db.execute_batch(sql)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,83 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require File.dirname(__FILE__) + '/DB'
4
+ require 'sqlite3'
5
+
6
+ module Termtter::Storage
7
+ class Status
8
+ KEYS = %w[post_id created_at in_reply_to_status_id in_reply_to_user_id post_text user_id screen_name]
9
+
10
+ def self.size
11
+ DB.instance.db.get_first_value("select count(*) from post").to_i
12
+ end
13
+
14
+ def self.search(query)
15
+ raise "query must be Hash(#{query}, #{query.class})" unless query.kind_of? Hash
16
+ if query[:text] == nil then
17
+ query[:text] = '';
18
+ end
19
+ if query[:user] == nil then
20
+ query[:user] = '';
21
+ end
22
+ result = []
23
+ sql = "select created_at, screen_name, post_text, in_reply_to_status_id, post_id, user_id "
24
+ sql += "from post inner join user on post.user_id = user.id where post_text like '%' || ? || '%'"
25
+ DB.instance.db.execute(sql,
26
+ query[:text]) do |created_at, screen_name, post_text, in_reply_to_status_id, post_id, user_id|
27
+ created_at = Time.at(created_at).to_s
28
+ result << {
29
+ :id => post_id,
30
+ :created_at => created_at,
31
+ :text => post_text,
32
+ :in_reply_to_status_id => in_reply_to_status_id,
33
+ :in_reply_to_user_id => nil,
34
+ :user => {
35
+ :id => user_id,
36
+ :screen_name => screen_name
37
+ }
38
+ }
39
+ end
40
+ Rubytter.json_to_struct(result)
41
+ end
42
+
43
+ def self.search_user(query)
44
+ raise "query must be Hash(#{query}, #{query.class})" unless query.kind_of? Hash
45
+ result = []
46
+ sql = "select created_at, screen_name, post_text, in_reply_to_status_id, post_id, user_id "
47
+ sql += "from post inner join user on post.user_id = user.id where "
48
+ sql += query[:user].split(' ').map!{|que| que.gsub(/(\w+)/, 'screen_name like \'%\1%\'')}.join(' or ')
49
+ DB.instance.db.execute(sql) do |created_at, screen_name, post_text, in_reply_to_status_id, post_id, user_id|
50
+ created_at = Time.at(created_at).to_s
51
+ result << {
52
+ :id => post_id,
53
+ :created_at => created_at,
54
+ :text => post_text,
55
+ :in_reply_to_status_id => in_reply_to_status_id,
56
+ :in_reply_to_user_id => nil,
57
+ :user => {
58
+ :id => user_id,
59
+ :screen_name => screen_name
60
+ }
61
+ }
62
+ end
63
+ Rubytter.json_to_struct(result)
64
+ end
65
+
66
+ def self.insert(data)
67
+ return unless data[:text]
68
+ DB.instance.db.execute(
69
+ "insert into post values(?,?,?,?,?,?)",
70
+ data[:post_id],
71
+ data[:created_at],
72
+ data[:in_reply_to_status_id],
73
+ data[:in_reply_to_user_id],
74
+ data[:text],
75
+ data[:user_id])
76
+ DB.instance.db.execute(
77
+ "insert into user values(?,?)",
78
+ data[:user_id],
79
+ data[:screen_name])
80
+ rescue SQLite3::SQLException
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ require 'sqlite3'
4
+
5
+ module Termtter::Storage
6
+ class Status
7
+ def initialize
8
+ end
9
+
10
+ def self.create
11
+ end
12
+
13
+ def self.all
14
+ []
15
+ end
16
+
17
+ def self.search
18
+ end
19
+
20
+ private
21
+ def db
22
+ @db ||= connect
23
+ end
24
+
25
+ def connect
26
+ @db = SQLite3::Database.new(File.expand_path('~/test.db'))
27
+ @db.type_translation = true
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,22 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ module Termtter::Client
4
+ register_command(
5
+ :name => :switch_user,
6
+ :exec_proc => lambda {|arg|
7
+ Termtter::API.switch_user(arg)
8
+ },
9
+ :completion_proc => lambda {|cmd, arg|
10
+ # TODO
11
+ },
12
+ :help => ["switch_user USERNAME", "Switch twitter account."]
13
+ )
14
+
15
+ register_command(
16
+ :name => :restore_user,
17
+ :exec_proc => lambda {|arg|
18
+ Termtter::API.restore_user
19
+ },
20
+ :help => ["restore_user", "Restore default twitter account."]
21
+ )
22
+ end