draftcode-termtter 1.0.8

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 (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