Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' into work

Conflicts:
	lib/plugins/defaults/stdout.rb
  • Loading branch information...
commit 49baed1836bf1c8c7bdd73386d2e90aed2df7017 2 parents 358e272 + 8fb3337
@nanki nanki authored
Showing with 1,356 additions and 384 deletions.
  1. +1 −1  Rakefile
  2. +1 −1  VERSION
  3. +1 −1  lib/plugins/aa.rb
  4. +5 −12 lib/plugins/appendtitle.rb
  5. +25 −0 lib/plugins/basic.rb
  6. +32 −0 lib/plugins/copy.rb
  7. +1 −0  lib/plugins/defaults/auto_reload.rb
  8. +18 −0 lib/plugins/defaults/cache.rb
  9. +2 −1  lib/plugins/defaults/command_line.rb
  10. +15 −6 lib/plugins/defaults/fib.rb
  11. +5 −2 lib/plugins/defaults/retweet.rb
  12. +80 −54 lib/plugins/defaults/standard_commands.rb
  13. +15 −4 lib/plugins/defaults/stdout.rb
  14. +27 −0 lib/plugins/defaults/system.rb
  15. +13 −0 lib/plugins/dupu.rb
  16. +2 −1  lib/plugins/english.rb
  17. +4 −0 lib/plugins/erase.rb
  18. +17 −0 lib/plugins/error_log.rb
  19. +8 −1 lib/plugins/expand-tinyurl.rb
  20. +5 −8 lib/plugins/friends.rb
  21. +21 −0 lib/plugins/geo.rb
  22. +10 −7 lib/plugins/growl.rb
  23. +88 −0 lib/plugins/hatena_keyword_haiku.rb
  24. +40 −17 lib/plugins/irc_gw.rb
  25. +29 −26 lib/plugins/itunes.rb
  26. +23 −0 lib/plugins/mecab.rb
  27. +13 −0 lib/plugins/mudan_kinshi.rb
  28. +7 −0 lib/plugins/ndkn.rb
  29. +47 −0 lib/plugins/other_user.rb
  30. +75 −25 lib/plugins/reply_sound.rb
  31. +10 −0 lib/plugins/ruby-v.rb
  32. +2 −1  lib/plugins/say.rb
  33. +15 −0 lib/plugins/short_logger.rb
  34. +1 −1  lib/plugins/storage.rb
  35. +1 −1  lib/plugins/storage/status.rb
  36. +1 −1  lib/plugins/stream.rb
  37. +21 −0 lib/plugins/time_signal.rb
  38. +45 −24 lib/plugins/tinyurl.rb
  39. +1 −1  lib/plugins/train.rb
  40. +2 −0  lib/plugins/translation.rb
  41. +11 −0 lib/plugins/url.rb
  42. +121 −0 lib/plugins/user_stream.rb
  43. +28 −0 lib/plugins/whale.rb
  44. +5 −1 lib/termtter.rb
  45. +5 −5 lib/termtter/active_rubytter.rb
  46. +67 −32 lib/termtter/api.rb
  47. +26 −6 lib/termtter/client.rb
  48. +21 −12 lib/termtter/command.rb
  49. +8 −4 lib/termtter/config.rb
  50. +10 −2 lib/termtter/config_setup.rb
  51. +4 −2 lib/termtter/config_template.erb
  52. +13 −0 lib/termtter/crypt.rb
  53. +5 −2 lib/termtter/default_config.rb
  54. +7 −3 lib/termtter/hookable.rb
  55. +67 −19 lib/termtter/memory_cache.rb
  56. +129 −34 lib/termtter/rubytter_proxy.rb
  57. +22 −18 lib/termtter/system_extensions.rb
  58. +1 −1  spec/plugins/capital_update_spec.rb
  59. +1 −1  spec/plugins/cool_spec.rb
  60. +1 −1  spec/plugins/curry_spec.rb
  61. +1 −1  spec/plugins/db_spec.rb
  62. +1 −1  spec/plugins/defaults/hashtag_spec.rb
  63. +1 −1  spec/plugins/defaults/list_spec.rb
  64. +1 −1  spec/plugins/defaults/plugin_spec.rb
  65. +1 −1  spec/plugins/defaults/retweet_spec.rb
  66. +1 −1  spec/plugins/draft_spec.rb
  67. +1 −1  spec/plugins/english_spec_.rb
  68. +1 −1  spec/plugins/expand-tinyurl_spec.rb
  69. +1 −1  spec/plugins/fib_spec.rb
  70. +1 −1  spec/plugins/filter_spec_.rb
  71. +1 −1  spec/plugins/footer_spec.rb
  72. +1 −1  spec/plugins/gsub_spec.rb
  73. +1 −1  spec/plugins/haml_spec.rb
  74. +1 −1  spec/plugins/hi_spec.rb
  75. +1 −1  spec/plugins/md5pass_spec.rb
  76. +1 −1  spec/plugins/pause_spec.rb
  77. +1 −1  spec/plugins/primes_spec_.rb
  78. +1 −1  spec/plugins/shell_spec.rb
  79. +1 −1  spec/plugins/sl_spec_.rb
  80. +1 −1  spec/plugins/spam_spec.rb
  81. +1 −1  spec/plugins/standard_commands_spec.rb
  82. +2 −2 spec/plugins/storage/sqlite3_spec.rb
  83. +2 −2 spec/plugins/storage/status_spec_.rb
  84. +1 −1  spec/plugins/tinyurl_spec.rb
  85. +1 −1  spec/plugins/truncate_spec.rb
  86. +1 −1  spec/plugins/whois_spec_.rb
  87. +1 −1  spec/termtter/active_rubytter_spec.rb
  88. +1 −1  spec/termtter/api_spec.rb
  89. +1 −1  spec/termtter/client_spec.rb
  90. +1 −1  spec/termtter/command_spec.rb
  91. +1 −1  spec/termtter/config_setup_spec.rb
  92. +1 −1  spec/termtter/config_spec.rb
  93. +16 −0 spec/termtter/crypt_spec.rb
  94. +1 −1  spec/termtter/event_spec.rb
  95. +1 −1  spec/termtter/hook_spec.rb
  96. +1 −1  spec/termtter/hookable_spec.rb
  97. +1 −1  spec/termtter/memory_cache_spec.rb
  98. +1 −1  spec/termtter/optparse_spec.rb
  99. +15 −1 spec/termtter/rubytter_proxy_spec.rb
  100. +1 −1  spec/termtter/system_extensions/windows_spec.rb
  101. +1 −1  spec/termtter/system_extensions_spec.rb
  102. +1 −1  spec/termtter/task_manager_spec.rb
  103. +1 −1  spec/termtter/task_spec.rb
  104. +1 −1  spec/termtter_spec.rb
View
2  Rakefile
@@ -11,7 +11,7 @@ begin
gem.add_dependency("json", ">= 1.1.3")
gem.add_dependency("highline", ">= 1.5.0")
gem.add_dependency("termcolor", ">= 1.0.0")
- gem.add_dependency("rubytter", ">= 0.11.0")
+ gem.add_dependency("rubytter", ">= 1.4.0")
gem.add_dependency("notify", ">= 0.2.1")
gem.authors = %w(jugyo ujihisa)
gem.email = 'jugyo.org@gmail.com'
View
2  VERSION
@@ -1 +1 @@
-1.7.2
+1.8.0
View
2  lib/plugins/aa.rb
@@ -34,6 +34,7 @@ def self.make
Termtter::Client.register_command(
:name => :aa,
+ :author => 'hitode909',
:exec_proc => lambda {|arg|
name = Termtter::Client.normalize_as_user_name(arg)
command = name.length > 0 ? "u @#{name} #{AAMaker.make}" : "u #{AAMaker.make}"
@@ -41,4 +42,3 @@ def self.make
},
:help => ["aa [(Optinal) USER]", "Post a AA"]
)
-
View
17 lib/plugins/appendtitle.rb
@@ -9,33 +9,26 @@
module Termtter::Client
config.plugins.appendtitle.set_default(:timeout, 30)
config.plugins.appendtitle.set_default(:cache_expire, 3600 * 24 * 7)
- config.plugins.appendtitle.set_default(:memcached_server, 'localhost:11211')
-
- def self.memcache_client
- @memcache_client ||= MemCache.new(config.plugins.appendtitle.memcached_server)
- end
def self.fetch_title(uri)
return unless uri
- key = %w{ termtter plugins appendtitle title}.push(Digest::SHA1.hexdigest(uri)).join('-')
- if v = memcache_client.get(key)
+ key = %w{ plugins appendtitle title}.push(Digest::SHA1.hexdigest(uri)).join('-')
+ if v = memory_cache.get(key)
logger.debug "appendtitle: cache hit for #{uri}"
return v
end
- memcache_client.set(key, '', config.plugins.appendtitle.cache_expire) # to avoid duplicate fetch
+ memory_cache.set(key, '', config.plugins.appendtitle.cache_expire) # to avoid duplicate fetch
begin
logger.debug "appendtitle: fetching title for #{uri}"
source = Nokogiri(open(uri).read)
if source and source.at('title')
title = source.at('title').text
- memcache_client.set(key, title, config.plugins.appendtitle.cache_expire)
+ memory_cache.set(key, title, config.plugins.appendtitle.cache_expire)
return title
end
nil
- rescue Timeout::Error
- nil
- rescue
+ rescue Timeout::Error, StandardError
nil
end
end
View
25 lib/plugins/basic.rb
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+config.access_token = ''
+config.access_token_secret = ''
+
+module OAuth
+ class AccessToken
+ def initialize(consumer, access_token, access_token_secret)
+ end
+ end
+end
+module Termtter
+ class RubytterProxy
+ def initialize(access_token, twitter_option)
+ user_name = config.plugins.basic.user_name
+ password = config.plugins.basic.password
+ @rubytter = Rubytter.new(user_name, password, twitter_option)
+ end
+ end
+end
+
+# basic.rb
+# Use Basic Auth instead of OAuth
+#
+# config.plugins.basic.user_name = 'your_name'
+# config.plugins.basic.password = 'the secret'
View
32 lib/plugins/copy.rb
@@ -0,0 +1,32 @@
+config.plugins.copy.set_default(:style, "@<%= t.user.screen_name %>: <%= t.text %> [ <%= url %> ]")
+
+def copy_to_clipboard(str)
+ if /darwin/i =~ RUBY_PLATFORM
+ IO.popen("pbcopy", "w") do |io|
+ io.print str
+ end
+ else
+ puts "Sorry, this plugin is only in Mac OS X."
+ end
+ str
+end
+
+Termtter::Client.plug 'url'
+
+Termtter::Client.register_command(:name => :copy,
+ :exec => lambda do |arg|
+ t = Termtter::API.twitter.show(arg)
+ url = url_by_tweet(t)
+ erbed_text = ERB.new(config.plugins.copy.style).result(binding)
+
+ puts "Copied=> #{copy_to_clipboard(erbed_text)}"
+end)
+
+Termtter::Client.register_command(:name => :copy_url,
+ :exec => lambda do |arg|
+ t = Termtter::API.twitter.show(arg)
+ url = url_by_tweet(t)
+
+ puts "Copied=> #{copy_to_clipboard(url)}"
+
+end)
View
1  lib/plugins/defaults/auto_reload.rb
@@ -4,6 +4,7 @@
Termtter::Client.execute('reload -r')
rescue TimeoutError
# do nothing
+ rescue
rescue Exception => e
Termtter::Client.handle_error(e)
end
View
18 lib/plugins/defaults/cache.rb
@@ -0,0 +1,18 @@
+require 'pp'
+
+module Termtter::Client
+ register_command(
+ :name => :"cache stats",
+ :help => ['cache stats', 'Show Memcached stats.'],
+ :exec_proc => lambda {|arg|
+ puts memory_cache.stats.pretty_inspect
+ })
+
+ register_command(
+ :name => :"cache flush",
+ :help => ['cache flush', 'Flush all caches.'],
+ :exec_proc => lambda {|arg|
+ memory_cache.flush_all
+ logger.info "cache flushed."
+ })
+end
View
3  lib/plugins/defaults/command_line.rb
@@ -64,6 +64,7 @@ def start_input_thread
Client.handle_error(e)
end
end
+ Client.exit
end
@input_thread.join
end
@@ -113,7 +114,7 @@ def setup_readline
def trap_setting()
return if /mswin(?!ce)|mingw|bccwin/ =~ RUBY_PLATFORM
-
+
begin
stty_save = `stty -g`.chomp
trap("INT") do
View
21 lib/plugins/defaults/fib.rb
@@ -1,7 +1,16 @@
def fib(n)i=0;j=1;n.times{j=i+i=j};i end
-Termtter::Client.register_command(:fib) do |arg|
- n = arg.to_i
- text = "fib(#{n}) = #{fib n}"
- Termtter::API.twitter.update(text)
- puts "=> " << text
-end
+Termtter::Client.register_command(:name => :fib,
+ :aliases => [:f, :ho],
+ :exec => lambda do |arg|
+ case arg
+ when "ukumori"
+ puts 'Does it mean "Sora Harakami (@sora_h)"?'
+ when "ootsuite", "otsuite"
+ puts "NDA :D"
+ else
+ n = arg.to_i
+ text = "fib(#{n}) = #{fib n}"
+ Termtter::API.twitter.update(text)
+ puts "=> " << text
+ end
+end)
View
7 lib/plugins/defaults/retweet.rb
@@ -8,10 +8,12 @@
:official_retweet, true)
config.plugins.retweet.set_default(
:quotetweet, false)
+config.plugins.retweet.set_default(
+ :as_reply, false)
module Termtter::Client
def self.post_retweet(s, comment = nil)
- s.user.protected and
+ s[:user][:protected] and
config.plugins.retweet.confirm_protected and
!confirm("#{s.user.screen_name} is protected! Are you sure?", false) and
return
@@ -33,7 +35,8 @@ def self.post_retweet(s, comment = nil)
comment += ' ' unless comment.nil?
rt_or_qt = (config.plugins.retweet.quotetweet and comment) ? 'QT' : 'RT'
text = ERB.new(config.plugins.retweet.format).result(binding)
- Termtter::API.twitter.update(text)
+ params = config.plugins.retweet.as_reply ? {:in_reply_to_status_id => s.id} : {}
+ Termtter::API.twitter.update(text, params)
puts "=> #{text}"
end
View
134 lib/plugins/defaults/standard_commands.rb
@@ -10,7 +10,7 @@
config.set_default(:easy_reply, false)
config.plugins.standard.set_default(
:one_line_profile_format,
- '<90>[<%=user_id%>]</90> <<%=color%>><%= user.screen_name %>: <%= padding %><%= (user.description || "").gsub(/\r?\n/, "") %></<%=color%>>')
+ '<90>[<%=user_id%>]</90> <%= mark %> <<%=color%>><%= user.screen_name %>: <%= padding %><%= (user.description || "").gsub(/\r?\n/, "") %></<%=color%>>')
module Termtter::Client
register_command(
@@ -32,26 +32,39 @@ module Termtter::Client
register_command(
:name => :update, :alias => :u,
:exec => lambda {|arg|
- unless arg.empty?
- params =
- if config.easy_reply && /^\s*(@\w+)/ =~ arg
- user_name = normalize_as_user_name($1)
- in_reply_to_status_id =
- Termtter::API.twitter.user(user_name).status.id rescue nil
- in_reply_to_status_id ?
- {:in_reply_to_status_id => in_reply_to_status_id} : {}
- else
- {}
- end
-
- result = Termtter::API.twitter.update(arg, params)
+ return if arg.empty?
+ params =
+ if config.easy_reply && /^\s*(@\w+)/ =~ arg
+ user_name = normalize_as_user_name($1)
+ in_reply_to_status_id =
+ Termtter::API.twitter.user(user_name).status.id rescue nil
+ in_reply_to_status_id ?
+ {:in_reply_to_status_id => in_reply_to_status_id} : {}
+ else
+ {}
+ end
- if result.text == arg
- puts "updated => #{result.text}"
+ # "u $aa msg" is likely to be a mistake of
+ # "re $aa msg".
+ if /^\s*\d+\s/ =~ arg
+ case HighLine.new.ask("Does it mean `re[ply] #{arg}` [N/y]? ")
+ when /^[yY]$/
+ Termtter::Client.execute("re #{arg}")
+ break
+ when /^[nN]?$/
else
- puts TermColor.parse("<red>Failed to update :(</red>")
+ puts "Invalid answer. Please input [yYnN] or nothing."
+ break
end
end
+
+ result = Termtter::API.twitter.update(arg, params)
+
+ if result.text == arg
+ puts "updated => #{result.text}"
+ else
+ puts TermColor.parse("<red>Failed to update :(</red>")
+ end
},
:help => ["update,u TEXT", "Post a new message"]
)
@@ -119,7 +132,7 @@ def method_missing(*args, &block)
end
def self.get_friends(user_name, max)
- self.get_friends_or_followers(:followers, user_name, max)
+ self.get_friends_or_followers(:friends, user_name, max)
end
def self.get_followers(user_name, max)
@@ -127,7 +140,7 @@ def self.get_followers(user_name, max)
end
def self.get_friends_or_followers(type, user_name, max)
- raise "type should :friends or :followers" unless [:friends, :followers].include? type
+ raise "type should be :friends or :followers" unless [:friends, :followers].include? type
users = []
cursor = -1
begin
@@ -150,7 +163,7 @@ def self.get_friends_or_followers(type, user_name, max)
)
register_command(
- :name => :followers, :aliases => [:following],
+ :name => :followers,
:exec_proc => lambda {|arg|
friends_or_followers_command(:followers, arg)
},
@@ -158,7 +171,7 @@ def self.get_friends_or_followers(type, user_name, max)
)
def self.friends_or_followers_command(type, arg)
- raise "type should :friends or :followers" unless [:friends, :followers].include? type
+ raise "type should be :friends or :followers" unless [:friends, :followers].include? type
limit = 20
if /\-([\d]+)/ =~ arg
limit = $1.to_i
@@ -171,7 +184,8 @@ def self.friends_or_followers_command(type, arg)
users.reverse.each{|user|
padding = ' ' * (longest - user.screen_name.length)
user_id = Termtter::Client.data_to_typable_id(user.id) rescue ''
- color = user.following ? 'BLACK' : 'RED'
+ color = user.following ? config.plugins.stdout.colors.first : config.plugins.stdout.colors.last
+ mark = user.following ? '' : ''
erbed_text = ERB.new(config.plugins.standard.one_line_profile_format).result(binding)
puts TermColor.unescape(TermColor.parse(erbed_text))
}
@@ -259,20 +273,20 @@ class SearchEvent < Termtter::Event; attr_reader :query; def initialize(query);
:exec_proc => lambda {|args|
args.split(' ').each do |arg|
user_name = normalize_as_user_name(arg)
- Termtter::API::twitter.follow(user_name)
- puts 'ok'
+ user = Termtter::API::twitter.follow(user_name)
+ puts "followed #{user.screen_name}"
end
},
:help => ['follow USER', 'Follow user']
)
register_command(
- :name => :leave, :aliases => [],
+ :name => :leave, :aliases => [:remove],
:exec_proc => lambda {|args|
args.split(' ').each do |arg|
user_name = normalize_as_user_name(arg)
- Termtter::API::twitter.leave(user_name)
- puts 'ok'
+ user = Termtter::API::twitter.leave(user_name)
+ puts "left #{user.screen_name}"
end
},
:help => ['leave USER', 'Leave user']
@@ -283,8 +297,8 @@ class SearchEvent < Termtter::Event; attr_reader :query; def initialize(query);
:exec_proc => lambda {|args|
args.split(' ').each do |arg|
user_name = normalize_as_user_name(arg)
- Termtter::API::twitter.block(user_name)
- puts 'ok'
+ user = Termtter::API::twitter.block(user_name)
+ puts "blocked #{user.screen_name}"
end
},
:help => ['block USER', 'Block user']
@@ -295,43 +309,55 @@ class SearchEvent < Termtter::Event; attr_reader :query; def initialize(query);
:exec_proc => lambda {|args|
args.split(' ').each do |arg|
user_name = normalize_as_user_name(arg)
- Termtter::API::twitter.unblock(user_name)
- puts 'ok'
+ user = Termtter::API::twitter.unblock(user_name)
+ puts "unblocked #{user.screen_name}"
end
},
:help => ['unblock USER', 'Unblock user']
)
- help = ['favorite_list USERNAME', 'show user favorites']
+ help = ['favorites,favlist USERNAME', 'show user favorites']
register_command(:favorites, :alias => :favlist, :help => help) do |arg|
- output Termtter::API.twitter.favorites(arg), :user_timeline, :type => :favorite
+ output(Termtter::API.twitter.favorites(arg), Termtter::Event.new(:user_timeline, :type => :favorite))
end
register_command(
:name => :favorite, :aliases => [:fav],
- :exec_proc => lambda {|arg|
- id =
- case arg
- when /^\d+/
- arg.to_i
- when /^@([A-Za-z0-9_]+)/
- user_name = normalize_as_user_name($1)
- statuses = Termtter::API.twitter.user_timeline(user_name)
- return if statuses.empty?
- statuses[0].id
- when /^\/(.*)$/
- word = $1
- raise "Not implemented yet."
- else
- if public_storage[:typable_id] && typable_id?(arg)
- typable_id_convert(arg)
+ :exec_proc => lambda {|args|
+ args.split(' ').each do |arg|
+ id =
+ case arg
+ when /^\d+/
+ arg.to_i
+ when /^@([A-Za-z0-9_]+)/
+ user_name = normalize_as_user_name($1)
+ statuses = Termtter::API.twitter.user_timeline(user_name)
+ return if statuses.empty?
+ statuses[0].id
+ when %r{twitter.com/[A-Za-z0-9_]+/status(?:es)?/\d+}
+ status_id = URI.parse(arg).path.split(%{/}).last
+ when %r{twitter.com/[A-Za-z0-9_]+}
+ user_name = normalize_as_user_name(URI.parse(arg).path.split(%{/}).last)
+ statuses = Termtter::API.twitter.user_timeline(user_name)
+ return if statuses.empty?
+ statuses[0].id
+ when /^\/(.*)$/
+ word = $1
+ raise "Not implemented yet."
else
- return
+ if public_storage[:typable_id] && typable_id?(arg)
+ typable_id_convert(arg)
+ else
+ return
+ end
end
+ begin
+ r = Termtter::API.twitter.favorite id
+ puts "Favorited status ##{r.id} on user @#{r.user.screen_name} #{r.text}"
+ rescue => e
+ handle_error e
end
-
- r = Termtter::API.twitter.favorite id
- puts "Favorited status ##{r.id} on user @#{r.user.screen_name} #{r.text}"
+ end
},
:completion_proc => lambda {|cmd, arg|
case arg
@@ -441,7 +467,7 @@ def self.formatted_help(helps)
:alias => :plugin,
:exec_proc => lambda {|arg|
if arg.empty?
- plugin_list
+ puts plugin_list.join(', ')
return
end
begin
View
19 lib/plugins/defaults/stdout.rb
@@ -4,19 +4,22 @@
require 'erb'
require 'tempfile'
+config.plugins.stdout.set_default(:gray, 90)
config.plugins.stdout.set_default(:colors, (31..36).to_a + (91..96).to_a)
config.plugins.stdout.set_default(
:timeline_format,
[
- '<90><%=time%> [<%=status_id%>]</90> ',
+ '<<%=config.plugins.stdout.gray||config.plugins.stdout.colors.last%>>',
+ '<%=time%> [<%=status_id%>] ',
+ '</<%=config.plugins.stdout.gray||config.plugins.stdout.colors.last%>>',
'||>',
'<<%=color%>><%=s.user.screen_name%></<%=color%>>: ',
'<%=text%> ',
- '<90>',
+ '<<%=config.plugins.stdout.gray||config.plugins.stdout.colors.last%>>',
'<%=reply_to_status_id ? " (reply_to [#{reply_to_status_id}]) " : ""%>',
'<%=retweeted_status_id ? " (retweet_to [#{retweeted_status_id}]) " : ""%>',
- '<%=source%><%=s.user.protected ? "[P]" : ""%>',
- '</90>'
+ '<%=source%><%=s[:user][:protected] ? "[P]" : ""%>',
+ '</<%=config.plugins.stdout.gray||config.plugins.stdout.colors.last%>>',
].join('')
)
config.plugins.stdout.set_default(:sweets, %w[jugyo ujm sora_h lingr_termtter termtter hitode909 nanki sixeight])
@@ -99,6 +102,10 @@ def call(statuses, event)
print_statuses(statuses, event)
end
+ def inspect
+ "#<Termtter::StdOut @name=#{@name}, @points=#{@points.inspect}, @exec_proc=#{@exec_proc.inspect}>"
+ end
+
private
def print_statuses(statuses, event, sort = true, time_format = nil)
return unless statuses and statuses.first
@@ -256,6 +263,10 @@ def screen_name_to_hash(screen_name)
def color_of_screen_name_cache
@color_of_screen_name_cache ||= {}
end
+
+ def escape(data)
+ data.gsub(/[:cntrl:]/) {|c| c == "\n" ? c : c.dump[1...-1]}.untaint
+ end
end
Client.register_hook(StdOut.new)
View
27 lib/plugins/defaults/system.rb
@@ -0,0 +1,27 @@
+module Termtter::Client
+ register_command('hook list') do
+ name_max_size = hooks.keys.map{|i|i.size}.max
+ points_max_size = hooks.values.map{|i|i.points.join(', ').size}.max
+ hooks.each do |name, hook|
+ points = "[#{hook.points.join(', ')}]"
+ puts "#{name.to_s.ljust(name_max_size)} " +
+ "<90>=&gt;</90>".termcolor + " #{points.ljust(points_max_size)} " +
+ "<90>=&gt;</90>".termcolor + " #{hook.exec_proc}"
+ end
+ end
+
+ register_command('hook remove') do |name|
+ puts remove_hook(name) ? "removed => #{name}" : '<red>hook not found!</red>'.termcolor
+ end
+
+ register_command('command list') do
+ name_max_size = commands.keys.map{|i|i.size}.max
+ commands.each do |name, command|
+ puts "#{name.to_s.ljust(name_max_size)} " + "<90>=&gt;</90>".termcolor + " #{command.exec_proc}"
+ end
+ end
+
+ register_command('command remove') do |name|
+ puts remove_command(name) ? "removed => #{name}" : '<red>command not found!</red>'.termcolor
+ end
+end
View
13 lib/plugins/dupu.rb
@@ -0,0 +1,13 @@
+# Maintainer: Sora Harakami
+# http://gist.github.com/453709
+
+Termtter::Client.register_command(:name => :dupu, :exec => lambda do |body|
+ #Termtter::Client.execute("update #{arg.chars.inject([]){|r,v|r << v*(5+rand(3)}.join}")
+ body.chomp!
+ max = 140
+ len = body.split(//).length
+ mod = max % len
+ ext = (0...len).to_a.sort_by{ rand }.take(mod)
+ res = body.split(//).each_with_index.map{ |c, i| c * (max / len + (ext.include?(i) ? 1 : 0)) }.join('')
+ Termtter::Client.execute("update #{res}")
+end)
View
3  lib/plugins/english.rb
@@ -2,7 +2,8 @@
# vim: set fenc=utf-8
module Termtter::English
- # english? :: String -> Boolean
+ # call-seq:
+ # english? :: String -> Boolean
def self.english?(message)
/[一-龠]+|[ぁ-ん]+|[ァ-ヴー]+|[a-zA-Z0-9]+/ !~ message
end
View
4 lib/plugins/erase.rb
@@ -0,0 +1,4 @@
+Termtter::Client.register_command('erase') do |arg|
+ num = /(\d+)/ =~ arg ? $1.to_i : 1
+ print "\e[#{num + 1}F\e[0J"
+end
View
17 lib/plugins/error_log.rb
@@ -0,0 +1,17 @@
+config.plugins.error_log.set_default(:file, File.expand_path("~/.termtter/error.log.txt"))
+
+Termtter::Client.register_hook(
+ :name => :error_log,
+ :point => :on_error,
+ :exec => lambda do |e|
+ open(config.plugins.error_log.file,"a") do |f|
+ f.puts "#{Time.now} ---------------------"
+ f.puts " #{e.class.to_s}: #{e.message}"
+ begin
+ e.backtrace.each do |s|
+ f.puts " #{s}"
+ end
+ rescue NoMethodError; end
+ end
+ end
+)
View
9 lib/plugins/expand-tinyurl.rb
@@ -12,6 +12,13 @@
{:host => "ow.ly", :pattern => %r'(http://ow\.ly(/[\w/]+))'},
{:host => "u.nu", :pattern => %r'(http://u\.nu(/[\w/]+))'},
{:host => "twurl.nl", :pattern => %r'(http://twurl\.nl(/\w+))'},
+ {:host => "icio.us", :pattern => %r'(http://icio\.us(/\w+))'},
+ {:host => "htn.to", :pattern => %r'(http://htn\.to(/\w+))'},
+ {:host => "cot.ag", :pattern => %r'(http://cot\.ag(/\w+))'},
+ {:host => "ht.ly", :pattern => %r'(http://ht\.ly(/\w+))'},
+ {:host => "p.tl", :pattern => %r'(http://p\.tl(/\w+))'},
+ {:host => "url4.eu", :pattern => %r'(http://url4\.eu(/\w+))'},
+ {:host => "t.co", :pattern => %r'(http://t\.co(/\w+))'},
]
config.plugins.expand_tinyurl.set_default(:shortters, [])
@@ -39,7 +46,7 @@ def expand_url(host, path)
res = Termtter::HTTPpool.start(host) do |h|
h.get(path, { 'User-Agent' => 'Mozilla' })
end
- return nil unless res.code == "301" or res.code == "302"
+ return nil unless res.code =~ /\A30/
newurl = res['Location']
newurl.respond_to?(:force_encoding) ? newurl.force_encoding(Encoding::UTF_8) : newurl
rescue Exception => e
View
13 lib/plugins/friends.rb
@@ -4,14 +4,11 @@ module Termtter::Client
:name => :diff_follow, :aliases => [:diff],
:exec_proc => lambda {|arg|
user_name = arg.empty? ? config.user_name : arg
- friends = public_storage[:friends]
- followers = public_storage[:followers]
- if friends.nil? || followers.nil?
- puts 'Do followers and friends first.'
- return
- end
- friends = friends.map(&:screen_name)
- followers = followers.map(&:screen_name)
+ puts "getting friends"
+ friends = get_friends(user_name, 2000).map(&:screen_name)
+ puts "getting followers"
+ followers = get_followers(user_name, 2000).map(&:screen_name)
+ puts
puts "friends - followers:"
puts (friends - followers).map{|s|"http://#{config.host}/#{s}"}.join("\n")
puts
View
21 lib/plugins/geo.rb
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+config.plugins.geo.set_default(:url, 'http://www.openstreetmap.org/?lat=%s&lon=%s&zoom=18')
+# other setting example: 'http://maps.google.co.jp/maps?q=%s,%s'
+
+Termtter::Client.register_hook(
+ :name => :geo,
+ :points => [:output],
+ :exec_proc => lambda {|statuses, event|
+ config.plugins.geo.path
+ if event == :show
+ statuses.each do |s|
+ next unless s.geo
+ next if s.geo[:type] != 'Point'
+ open_browser(config.plugins.geo.url % s.geo.coordinates)
+ end
+ end
+ }
+)
+
+# geo.rb:
+# show the location of the tweet
View
17 lib/plugins/growl.rb
@@ -93,13 +93,16 @@ def get_icon_path(s)
# TODO: Add option for priority and sticky
system 'growlnotify', s.user.screen_name, '-m', s.text.gsub("\n",''), '-n', 'termtter', '--image', get_icon_path(s)
else
- growl.notify(
- "update_friends_timeline",
- s.user.screen_name,
- CGI.unescapeHTML(s.text),
- config.plugins.growl.priority,
- config.plugins.growl.sticky
- )
+ begin
+ growl.notify(
+ "update_friends_timeline",
+ s.user.screen_name,
+ CGI.unescapeHTML(s.text),
+ config.plugins.growl.priority,
+ config.plugins.growl.sticky
+ )
+ rescue Errno::ECONNREFUSED
+ end
end
sleep 0.1
end
View
88 lib/plugins/hatena_keyword_haiku.rb
@@ -0,0 +1,88 @@
+# -*- coding: utf-8 -*-
+require 'nkf'
+require 'open-uri'
+
+module HatenaKeywordHaiku
+ class Word
+ def initialize(word, yomi)
+ raise 'word is nil' unless word and not word.empty?
+ @word = word
+ @yomi = yomi
+ end
+
+ def word
+ @word
+ end
+
+ def yomi
+ @yomi
+ end
+
+ def length
+ @length ||= self.yomi.gsub(/\n|ぁ|ぃ|ぅ|ぇ|ぉ|ァ|ィ|ゥ|ェ|ォ|ゃ|ゅ|ょ|ャ|ュ|ョ/, '').split(//).length
+ end
+ end
+
+ @@words = nil
+
+ def self.generate(*args)
+ args = [5,7,5] if args.empty?
+
+ args.map{ |len|
+ words[len.to_i].choice rescue raise "No word which length is #{len}"
+ }.map{ |w| w.word }.join(' ')
+ end
+
+ # http://d.hatena.ne.jp/hatenadiary/20060922/1158908401
+ def self.setup(csv_path = '/tmp/keywordlist_furigana.csv', csv_url = 'http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv')
+ return if @@words
+ @@words = { }
+ csv_path = File.expand_path(csv_path)
+ unless File.exists? csv_path
+ puts "haiku: downloading CSV"
+ open(csv_path, 'w'){ |f|
+ f.write(open(csv_url).read)
+ }
+ end
+
+
+ puts "haiku: parsing CSV"
+ open(csv_path).each_line{ |line|
+ yomi, word = *NKF.nkf('-w', line.chomp).split(/\t/)
+ next unless yomi and word
+ w = Word.new(word, yomi)
+ @@words[w.length] = [] unless @@words.has_key? w.length
+ @@words[w.length].push w
+ }
+ puts "haiku: setup done"
+ @@words
+ end
+
+ def self.words
+ setup unless @@words
+ @@words
+ end
+end
+
+Thread.new{
+ HatenaKeywordHaiku.setup('~/.termtter/keywordlist_furigana.csv')
+}
+Termtter::Client.register_command(
+ :name => :hatena_keyword_haiku,
+ :aliases => [:haiku],
+ :author => 'hitode909',
+ :exec_proc => lambda {|arg|
+ args = arg.split(/\s+/)
+
+ name = ''
+ if args.first and not args.first =~ /^\d+$/
+ name = Termtter::Client.normalize_as_user_name(args.shift)
+ command = "u @#{name} #{HatenaKeywordHaiku.generate(*args)}"
+ else
+ command = "u #{HatenaKeywordHaiku.generate(*args)}"
+ end
+
+ Termtter::Client.execute command
+ },
+ :help => ['haiku [(Optinal) USER] [(Optional) 5 7 5 7 7]', 'Post a Haiku']
+)
View
57 lib/plugins/irc_gw.rb
@@ -2,6 +2,7 @@
require 'net/irc'
require 'set'
+require 'cgi'
config.plugins.irc_gw.set_default(:port, 16669)
config.plugins.irc_gw.set_default(:last_statuses_count, 100)
@@ -34,7 +35,7 @@ class TermtterIrcGateway < Net::IRC::Server::Session
@@last_statuses = []
Termtter::Client.register_hook(
- :name => :irc_gw,
+ :name => :irc_gw_output,
:point => :output,
:exec => lambda { |statuses, event|
if event == :update_friends_timeline
@@ -47,6 +48,15 @@ class TermtterIrcGateway < Net::IRC::Server::Session
end
}
)
+ Termtter::Client.register_hook(
+ :name => :irc_gw_handle_error,
+ :point => :on_error,
+ :exec => lambda { |error|
+ @@listners.each{ |listener|
+ listener.log "[ERROR] #{error.class.to_s}: #{error.message}"
+ }
+ }
+ )
if Termtter::Client.respond_to? :register_output
Termtter::Client.register_output(:irc) do |message|
@@listners.each do |listener|
@@ -62,16 +72,17 @@ def main_channel; '#termtter' end
def initialize(*args)
super
@@listners << self
- @friends = Set.new
+ @members = Set.new
@commands = []
Termtter::Client.register_hook(:collect_user_names_for_irc_gw, :point => :pre_filter) do |statuses, event|
new_users = []
statuses.each do |s|
screen_name = s.user.screen_name
- next if screen_name == @user # XXX
- next if @friends.include? screen_name
- @friends << screen_name
+ next if screen_name == config.user_name
+ next unless friends_ids.include? s.user.id
+ next if @members.include? screen_name
+ @members << screen_name
new_users << screen_name
end
join_members(new_users)
@@ -99,17 +110,15 @@ def call(statuses, event)
statuses.each do |s|
typable_id = Termtter::Client.data_to_typable_id(s.id)
time = Time.parse(s.created_at).strftime(time_format) if time_format
- post s.user.screen_name, msg_type, main_channel, [time, s.text, typable_id].compact.join(' ')
+ post s.user.screen_name, msg_type, main_channel, [time, CGI.unescapeHTML(s.text), typable_id].compact.join(' ')
end
end
def on_message(m)
termtter_command = m.command.downcase + ' ' + m.params.join(' ')
return unless Termtter::Client.find_command(termtter_command)
- post '#termtter', NOTICE, main_channel, '> ' + termtter_command
- Termtter::Client.execute(termtter_command)
+ execute_command(termtter_command)
rescue Exception => e
- post '#termtter', NOTICE, main_channel, "#{e.class.to_s}: #{e.message}"
Termtter::Client.handle_error(e)
end
@@ -127,8 +136,7 @@ def on_privmsg(m)
if message =~ / +\//
termtter_command = message.gsub(/ +\//, '')
return unless Termtter::Client.find_command(termtter_command)
- post '#termtter', NOTICE, main_channel, '> ' + termtter_command
- Termtter::Client.execute(termtter_command)
+ execute_command(termtter_command)
return
end
config.plugins.irc_gw.command_regexps and
@@ -136,18 +144,25 @@ def on_privmsg(m)
if message =~ rule
command = message.scan(rule).first.join(' ')
next unless Termtter::Client.find_command(command)
- post '#termtter', NOTICE, main_channel, '> ' + command
- Termtter::Client.execute(command)
+ execute_command(command)
return
end
end
- Termtter::Client.execute('update ' + message)
+ execute_command('update ' + message)
post @prefix, TOPIC, main_channel, message
rescue Exception => e
- post '#termtter', NOTICE, main_channel, "#{e.class.to_s}: #{e.message}"
Termtter::Client.handle_error(e)
end
+ def execute_command(command)
+ original_confirm = config.confirm
+ config.confirm = false
+ post '#termtter', NOTICE, main_channel, '> ' + command
+ Termtter::Client.execute(command)
+ ensure
+ config.confirm = original_confirm
+ end
+
def log(str)
str.each_line do |line|
post server_name, NOTICE, main_channel, line
@@ -155,11 +170,11 @@ def log(str)
end
def sync_friends
- previous_friends = @friends
+ previous_friends = @members
new_friends = Termtter::Client.following_friends
diff = new_friends - previous_friends
join_members(diff)
- @friends += diff
+ @members += diff
end
def sync_commands
@@ -184,6 +199,14 @@ def join_members(members)
post server_name, MODE, main_channel, "+#{"v" * params.size}", *params unless params.empty?
end
+ def friends_ids
+ if !@friends_ids || !@friends_ids_expire ||@friends_ids_expire < Time.now
+ @friends_ids = Termtter::API.twitter.friends_ids(config.user_name)
+ @friends_ids_expire = Time.now + 3600
+ end
+ @friends_ids
+ end
+
end
unless defined? IRC_SERVER
View
55 lib/plugins/itunes.rb
@@ -1,33 +1,36 @@
# -*- coding: utf-8 -*-
-require 'appscript' or raise 'itunes plugin cannot run'
+begin
+ require 'appscript'
+rescue LoadError
+ raise "itunes plug: can't load appscript gem. please run 'gem install rb-appscript'"
+end
config.plugins.itunes.set_default(:prefix, 'Listening now:')
config.plugins.itunes.set_default(:suffix, '#iTunes #listening')
-config.plugins.itunes.set_default(
- :format,
+config.plugins.itunes.set_default(:format,
'<%=prefix%> <%=track_name%> (<%=time%>) <%=artist%> <%=album%> <%=suffix%>')
-module Termtter::Client
- register_command :name => :listening_now, :aliases => [:ln],
- :help => ['listening_now,ln', "Post the information of listening now."],
- :exec_proc => lambda {|args|
- begin
- prefix = config.plugins.itunes.prefix
- track_name = Appscript.app('iTunes').current_track.name.get
- artist = Appscript.app('iTunes').current_track.artist.get
- genre = Appscript.app('iTunes').current_track.genre.get
- time = Appscript.app('iTunes').current_track.time.get
- album = Appscript.app('iTunes').current_track.album.get
- suffix = config.plugins.itunes.suffix
- erbed_text = ERB.new(config.plugins.itunes.format).result(binding)
- erbed_text.gsub!(/\s{2,}/, ' ')
- if args.length > 0
- erbed_text = args + ' ' + erbed_text
- end
- Termtter::API.twitter.update(erbed_text)
- puts "=> " << erbed_text
- rescue => e
- p e
+Termtter::Client.register_command(
+ :name => :listening_now, :aliases => [:ln, :itunes, :music, :m],
+ :help => ['listening_now,ln,itunes,music', "Post the information of listening now."],
+ :exec => lambda {|args|
+ begin
+ prefix = config.plugins.itunes.prefix
+ track_name = Appscript.app('iTunes').current_track.name.get
+ artist = Appscript.app('iTunes').current_track.artist.get
+ genre = Appscript.app('iTunes').current_track.genre.get
+ time = Appscript.app('iTunes').current_track.time.get
+ album = Appscript.app('iTunes').current_track.album.get
+ suffix = config.plugins.itunes.suffix
+ erbed_text = ERB.new(config.plugins.itunes.format).result(binding)
+ erbed_text.gsub!(/\s{2,}/, ' ')
+ if args.length > 0
+ erbed_text = args + ' ' + erbed_text
end
- }
-end
+ Termtter::API.twitter.update(erbed_text)
+ puts "=> " << erbed_text
+ rescue => e
+ p e
+ end
+ }
+)
View
23 lib/plugins/mecab.rb
@@ -0,0 +1,23 @@
+# coding: utf-8
+$KCODE = 'u' unless defined?(Encoding)
+# mecab('これはテストです') #=>
+# [["これ", "名詞", "代名詞", "一般", "*", "*", "*", "これ", "コレ", "コレ"],
+# ["は", "助詞", "係助詞", "*", "*", "*", "*", "は", "ハ", "ワ"],
+# ["テスト", "名詞", "サ変接続", "*", "*", "*", "*", "テスト", "テスト", "テスト"],
+# ["です", "助動詞", "*", "*", "*", "特殊・デス", "基本形", "です", "デス", "デス"]]
+def mecab(str)
+ IO.popen('mecab', 'r+') {|io|
+ io.puts str
+ io.close_write
+ io.read.split(/\n/).map {|i| i.split(/\t|,/) }[0..-2]
+ }
+end
+
+Termtter::Client.register_command(
+ :name => 'mecab',
+ :help => 'post a Japanese message with syntaxtic explanations. Requirements: mecab command',
+ :exec => lambda {|arg|
+ text = mecab(arg).map {|i| "#{i[0]}(#{i[1]}: #{i[2]})" }.join + ' #mecab'
+ update(text)
+ puts "=> #{test}"
+ })
View
13 lib/plugins/mudan_kinshi.rb
@@ -0,0 +1,13 @@
+# coding: utf-8
+# http://twitter.com/ototorosama/status/14283311303
+
+Termtter::Client.register_hook(
+ :name => :mudan_kinshi,
+ :author => 'Sora Harakami',
+ :point => :pre_coloring,
+ :exec => lambda {|r,e|
+ r.gsub(/無断(.+?)禁止/) {|s|
+ "か、勝手に#{$1}しないでよね!! ...バカ....."
+ }
+ }
+)
View
7 lib/plugins/ndkn.rb
@@ -0,0 +1,7 @@
+Termtter::Client.register_command(
+ :name => :ndkn,
+ :exec => lambda do |arg|
+ n = Termtter::Crypt.crypt(arg)
+ puts "ndkned => #{n.inspect}"
+ end
+)
View
47 lib/plugins/other_user.rb
@@ -0,0 +1,47 @@
+require 'base64'
+
+config.plugins.other_user.set_default(:accounts,{})
+config.plugins.other_user.set_default(:tokens_file,"~/.termtter/other_user_tokens")
+config.plugins.other_user.set_default(:alias,{})
+
+Termtter::Client.register_command(
+ :name => :other_user,
+ :alias => :o,
+ :help => ['other_user, o','Post by other user'],
+ :exec => lambda do |arg_raw|
+ tokens =
+ if File.exist?(File.expand_path(config.plugins.other_user.tokens_file))
+ Marshal.load(
+ Base64.decode64(File.read(File.expand_path(
+ config.plugins.other_user.tokens_file))))
+ else
+ {}
+ end
+
+ body = arg_raw.split(/ /)
+ user_raw = body.shift
+ user = config.plugins.other_user.alias[user_raw] || user_raw
+
+ unless tokens[user]
+ puts "<on_red>ERROR</on_red> #{user} isn't authorized yet. Starting authorization...".termcolor
+ tokens[user] = Termtter::API.authorize_by_oauth(false,false,false)
+ open(File.expand_path(config.plugins.other_user.tokens_file), 'w') do |f|
+ f.print Base64.encode64(Marshal.dump(tokens))
+ end
+ end
+
+ at = OAuth::AccessToken.new(
+ OAuth::Consumer.new(
+ Termtter::Crypt.decrypt(Termtter::CONSUMER_KEY),
+ Termtter::Crypt.decrypt(Termtter::CONSUMER_SECRET),
+ :site => "http://twitter.com/",
+ :proxy => ENV['http_proxy']),
+ tokens[user][:token],
+ tokens[user][:secret])
+
+ t = OAuthRubytter.new(at, Termtter::API.twitter_option)
+ t.update(body.join(' '))
+
+ puts "updated by #{user} => #{body.join(' ')}"
+ end
+)
View
100 lib/plugins/reply_sound.rb
@@ -1,33 +1,83 @@
#-*- coding: utf-8 -*-
-# FIXME: Currently this plugin is available only on Mac OS X.
+config.plugins.reply_sound.set_default(:interval, 600)
-if RUBY_PLATFORM =~ /darwin/i
- config.plugins.reply_sound.set_default(:interval, 600)
- config.plugins.reply_sound.set_default(
- :sound_file, '/System/Library/Sounds/Hero.aiff')
+nul_port = /mswin/i =~ RUBY_PLATFORM ? "NUL" : "/dev/null"
- reply_sound_cache = nil
- reply_sound_cache_ids = []
+reply_sound_cache = nil
+reply_sound_cache_ids = []
+not_supported = false
+cmd_ok = false
- Termtter::Client.add_task(
- :name => :reply_sound,
- :interval => config.plugins.reply_sound.interval) do
- replies = Termtter::API.twitter.replies
- new_replies = replies.delete_if {|x| reply_sound_cache_ids.index(x[:id]) }
- if !reply_sound_cache.nil? && new_replies.size > 0
- if respond_to? :spawn, true
- system 'afplay "'+config.plugins.reply_sound.sound_file+'" 2>/dev/null &'
+
+Termtter::Client.register_hook(
+ :name => :reply_sound_initialization,
+ :point => :initialize,
+ :exec => lambda do
+ case RUBY_PLATFORM
+ when /darwin/i
+ config.plugins.reply_sound.set_default(
+ :sound_file, '/System/Library/Sounds/Hero.aiff')
+ config.plugins.reply_sound.set_default(
+ :command, ['afplay', config.plugins.reply_sound.sound_file, {:out => nul_port, :err => nul_port}])
+ cmd_ok = true
+ when /linux/i
+ case `uname -v`.chomp
+ when /ubuntu/i
+ config.plugins.reply_sound.set_default(
+ :sound_file, '/usr/share/sounds/gnome/default/alerts/drip.ogg')
else
- spawn 'afplay', config.plugins.reply_sound.sound_file, :out => '/dev/null'
+ config.plugins.reply_sound.set_default(
+ :sound_file, '')
+ end
+ else
+ not_supported = true
+ puts TermColor.parse(
+ "<red>WARNING: Currently reply_sound plugin is not supported yet in your environment.</red>")
+ end
+
+ unless cmd_ok
+ begin
+ if /mplayer/i =~ `mplayer -v`.chomp
+ config.plugins.reply_sound.set_default(
+ :command, ['mplayer', config.plugins.reply_sound.sound_file, :out => nul_port, :err => nul_port])
+ cmd_ok = true
+ not_supported = false
+ end
+ rescue Errno::ENOENT
end
- Termtter::Client.output(
- new_replies, Termtter::Event.new(:new_replies,:type => :reply))
end
- reply_sound_cache = replies
- reply_sound_cache_ids += replies.map {|x| x[:id]}
- end
-else
- puts TermColor.parse(
- "<red>WARNING: Currently reply_sound plugin is available only on Mac OS X.</red>")
-end
+
+ unless not_supported
+ Termtter::Client.add_task(
+ :name => :reply_sound_wait,
+ :interval => 10) do
+ Termtter::Client.add_task(
+ :name => :reply_sound,
+ :interval => config.plugins.reply_sound.interval) do
+ cmd = config.plugins.reply_sound.command.kind_of?(Array) ?
+ config.plugins.reply_sound.command : [config.plugins.reply_sound.command]
+ replies = Termtter::API.twitter.replies
+ new_replies = replies.delete_if {|x| reply_sound_cache_ids.index(x[:id]) }
+ if !reply_sound_cache.nil? && new_replies.size > 0
+ if respond_to? :spawn, true
+ if respond_to? :fork, true
+ fork { exec *cmd }
+ else
+ system *cmd
+ end
+ else
+ spawn *cmd
+ end
+ print "\e[0G" + "\e[K" unless win?
+ Termtter::Client.output(
+ new_replies, Termtter::Event.new(:new_replies,:type => :reply))
+ Readline.refresh_line
+ end
+ reply_sound_cache = replies
+ reply_sound_cache_ids += replies.map {|x| x[:id]}
+ end
+ Termtter::Client.delete_task(:reply_sound_wait)
+ end
+ end
+ end)
View
10 lib/plugins/ruby-v.rb
@@ -0,0 +1,10 @@
+module Termtter::Client
+ register_command(
+ :name => 'ruby-v',
+ :author => 'ujihisa',
+ :help => ["ruby-v", "Post the Ruby version you are using"],
+ :exec_proc => lambda {|_|
+ result = Termtter::API.twitter.update("#{RUBY_DESCRIPTION} #termtterrubyversion")
+ puts "=> " << result.text
+ })
+end
View
3  lib/plugins/say.rb
@@ -2,7 +2,8 @@
raise 'say.rb runs only in OSX Leopard or Snow Leopard' if /darwin(9|10)/ !~ RUBY_PLATFORM
-# say :: String -> String -> IO ()
+# call-seq:
+# say :: String -> String -> IO ()
def say(who, what)
voices = %w(Alex Alex Bruce Fred Ralph Agnes Kathy Vicki)
voice = voices[who.hash % voices.size]
View
15 lib/plugins/short_logger.rb
@@ -0,0 +1,15 @@
+config.plugins.short_logger.set_default(:length, 30)
+
+Termtter::Client.register_hook(
+ :name => :setup_short_logger,
+ :point => :post_setup_logger,
+ :exec => lambda {
+ logger = Termtter::Client.logger
+ original = logger.formatter
+ length = config.plugins.short_logger.length
+ logger.formatter = lambda {|severity, time, progname, message|
+ message = message[0...length] + " ..." if message.size >= length unless config.devel
+ original.call(severity, time, progname, message)
+ }
+ }
+)
View
2  lib/plugins/storage.rb
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
require 'time'
-require File.dirname(__FILE__) + '/storage/db'
+require File.expand_path(File.dirname(__FILE__)) + '/storage/db'
module Termtter::Client
@db = Termtter::Storage::DB.new
View
2  lib/plugins/storage/status.rb
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
-require File.dirname(__FILE__) + '/DB'
+require File.expand_path(File.dirname(__FILE__)) + '/DB'
require 'sqlite3'
module Termtter::Storage
View
2  lib/plugins/stream.rb
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
require 'tweetstream'
-require File.dirname(__FILE__) + '/../termtter/active_rubytter'
+require File.expand_path(File.dirname(__FILE__)) + '/../termtter/active_rubytter'
config.plugins.stream.set_default :max_following, 400
config.plugins.stream.set_default :timeline_format, '<yellow>[S]</yellow> $orig'
View
21 lib/plugins/time_signal.rb
@@ -0,0 +1,21 @@
+config.plugins.time_signal.set_default(:minutes, [0])
+
+last_signal_time = nil
+
+Termtter::Client.add_task(:name => :time_signal, :interval => 10) do
+ begin
+ now = Time.now
+ if config.plugins.time_signal.minutes.include?(now.min)
+ hour = now.strftime('%H:%M')
+ unless hour == last_signal_time
+ print "\e[0G\e[2K"
+ puts "<on_green> #{hour} </on_green>".termcolor
+ Termtter::Client.notify 'time signal', hour
+ Readline.refresh_line
+ last_signal_time = hour
+ end
+ end
+ rescue Exception => e
+ Termtter::Client.handle_error(e)
+ end
+end
View
69 lib/plugins/tinyurl.rb
@@ -6,36 +6,57 @@
{ :host => "is.gd", :format => '/api.php?longurl=%s' },
{ :host => "tinyurl.com", :format => '/api-create.php?url=%s' },
])
+config.plugins.tinyurl.set_default(:ignore_regexp, %r{
+ \Ahttp://bit\.ly/ | \Ahttp://tinyurl\.com/ | \Ahttp://is\.gd/
+ | \Ahttp://ff\.im/ | \Ahttp://j\.mp/ | \Ahttp://goo\.gl/
+ | \Ahttp://tr\.im/ | \Ahttp://short\.to/ | \Ahttp://ow\.ly/
+ | \Ahttp://u\.nu/ | \Ahttp://twurl\.nl/ | \Ahttp://icio\.us/
+ | \Ahttp://htn\.to/ | \Ahttp://cot\.ag/ | \Ahttp://ht\.ly/ | \Ahttp://p\.tl/
+ | \Ahttp://url4\.eu/ | \Ahttp://t\.co/
+}x )
config.plugins.tinyurl.set_default(:tinyurl_hook_commands, [:update, :reply, :retweet])
-config.plugins.tinyurl.set_default(:uri_regexp, URI.regexp(%w(http https ftp)))
+config.plugins.tinyurl.set_default(
+ :uri_regexp,
+ /#{URI.regexp(%w(http https ftp))}\S*/ )
-Termtter::Client.register_hook(
- :name => :tinyurl,
- :points => config.plugins.tinyurl.tinyurl_hook_commands.map {|cmd|
- "modify_arg_for_#{cmd.to_s}".to_sym
- },
- :exec_proc => lambda {|cmd, arg|
- arg.split(' ').map {|component|
- result = component
- if component =~ config.plugins.tinyurl.uri_regexp
- url_enc = URI.escape(component, /[^a-zA-Z0-9.:]/)
+module Termtter::Client
+ register_hook(
+ :name => :tinyurl,
+ :points => config.plugins.tinyurl.tinyurl_hook_commands.map {|cmd|
+ "modify_arg_for_#{cmd.to_s}".to_sym
+ },
+ :exec_proc => lambda {|cmd, arg|
+ arg.gsub(config.plugins.tinyurl.uri_regexp) do |url|
+ result = nil
config.plugins.tinyurl.shorturl_makers.each do |site|
- res = Termtter::HTTPpool.start(site[:host]) do |h|
- h.get(site[:format] % url_enc)
- end
- if res.code == '200'
- result = res.body
- if result =~ /"shortUrl": "(http.*)"/
- result = $1
- end
- break
- end
+ result = shorten_url(url, site[:host], site[:format])
+ break if result
end
+ result or url
+ end
+ }
+ )
+
+ # returns nil if not shorten
+ def self.shorten_url(url, host, format)
+ return url if config.plugins.tinyurl.ignore_regexp =~ url # already shorten
+ url_enc = URI.escape(url, /[^a-zA-Z0-9.:]/)
+ res = Termtter::HTTPpool.start(host) do |h|
+ h.get(format % url_enc)
+ end
+ if res.code == '200'
+ result = res.body
+ if /"shortUrl": "(http.*?)"/ =~ result
+ result = $1
+ elsif /"statusCode": "ERROR"/ =~ result
+ return nil
end
result
- }.join(' ')
- }
-)
+ else
+ nil
+ end
+ end
+end
# tinyuri.rb
# make URLs in your update to convert tinyurl.com/XXXXXXX.
View
2  lib/plugins/train.rb
@@ -1,5 +1,5 @@
-# あ
# -*- coding: utf-8 -*-
+# あ
module Termtter::Client
def self.train(length)
text = "ε="
View
2  lib/plugins/translation.rb
@@ -6,6 +6,8 @@
require 'uri'
def translate(text, langpair)
+ text = Termtter::API.twitter.show(text)[:text] if /^\d+$/ =~ text
+
req = Net::HTTP::Post.new('/translate_t')
req.add_field('Content-Type', 'application/x-www-form-urlencoded')
req.add_field('User-Agent', 'Mozilla/5.0')
View
11 lib/plugins/url.rb
@@ -0,0 +1,11 @@
+def url_by_tweet(t)
+ "http://twitter.com/#{t.user.screen_name}/status/#{t.id}"
+end
+
+Termtter::Client.register_command(:name => :url,
+ :exec => lambda do |arg|
+ t = Termtter::API.twitter.show(arg)
+ puts url_by_tweet(t)
+end)
+
+
View
121 lib/plugins/user_stream.rb
@@ -0,0 +1,121 @@
+module Termtter::Client
+ register_command(:"user_stream stop", :help => 'user_stream stop') do |arg|
+ logger.info 'stopping user stream'
+ if @user_stream_thread
+ @user_stream_thread.exit
+ end
+ @user_stream_thread = nil
+ plug "defaults/auto_reload"
+ end
+
+ handle_chunk = lambda { |chunk|
+ data = Termtter::ActiveRubytter.new(JSON.parse(chunk)) rescue return
+ Termtter::Client.logger.debug "user_stream: received #{JSON.parse(chunk).inspect}"
+ begin
+ if data[:event]
+ if /list_/ =~ data[:event]
+ typable_id = Termtter::Client.data_to_typable_id(data.target.id)
+ puts "[%s] %s %s %s to %s]" %
+ [typable_id, data.source.screen_name, data.event, data.target.screen_name, data.target_object.uri]
+ return
+ end
+ if data[:target_object]
+ # target_object is status
+ source_user = data.source
+ status = data.target_object
+ typable_id = Termtter::Client.data_to_typable_id(status.id)
+ puts "[#{typable_id}] #{source_user.screen_name} #{data.event} #{status.user.screen_name}: #{status.text}"
+ else
+ # target is user
+ source_user = data.source
+ target_user = data.target
+ typable_id = Termtter::Client.data_to_typable_id(target_user.id)
+ puts "[#{typable_id}] #{source_user.screen_name} #{data.event} #{target_user.screen_name}"
+ end
+ elsif data[:friends]
+ puts "You have #{data[:friends].length} friends."
+ elsif data[:delete]
+ status = Termtter::API.twitter.safe.show(data.delete.status.id)
+ puts "#{status.user.screen_name} deleted: #{status.text}"
+ else
+ output([data], Termtter::Event.new(:update_friends_timeline, :type => :main))
+ Termtter::API.twitter.store_status_cache(data)
+ end
+ rescue Termtter::RubytterProxy::FrequentAccessError
+ # ignore
+ rescue Timeout::Error, StandardError => error
+ new_error = error.class.new("#{error.message} (#{JSON.parse(chunk).inspect})")
+ error.instance_variables.each{ |v|
+ new_error.instance_variable_set(v, error.instance_variable_get(v))
+ }
+ handle_error new_error
+ end
+ }
+
+ register_command(:"user_stream", :help => 'user_stream') do |arg|
+
+ uri = URI.parse('http://betastream.twitter.com/2b/user.json')
+
+ unless @user_stream_thread
+ logger.info 'checking API status'
+ 1.times{ # to use break
+ Termtter::HTTPpool.start(uri.host, uri.port){ |http|
+ request = Net::HTTP::Get.new(uri.request_uri)
+ request.oauth!(http, Termtter::API.twitter.consumer_token, Termtter::API.twitter.access_token)
+
+ http.request(request){ |response|
+ raise response.code.to_i unless response.code.to_i == 200
+ break
+ }
+ }
+ }
+ logger.info 'API seems working'
+ end
+
+ execute('user_stream stop') if @user_stream_thread
+ delete_task(:auto_reload)
+
+ @user_stream_thread = Thread.new {
+ loop do
+ begin
+ logger.info 'connecting to user stream'
+ Termtter::HTTPpool.start(uri.host, uri.port){ |http|
+ request = Net::HTTP::Get.new(uri.request_uri)
+ request.oauth!(http, Termtter::API.twitter.consumer_token, Termtter::API.twitter.access_token)
+ http.request(request){ |response|
+ raise response.code.to_i unless response.code.to_i == 200
+ raise 'Response is not chuncked' unless response.chunked?
+ response.read_body{ |chunk|
+ handle_chunk.call(chunk)
+ }
+ }
+ }
+ rescue Timeout::Error, StandardError => e
+ handle_error e
+ logger.info 'sleeping'
+ sleep 10
+ end
+ end
+ }
+ end
+
+ register_hook(
+ :name => :user_stream_init,
+ :author => '?', # FIXME
+ :point => :initialize,
+ :exec => lambda {
+ execute('user_stream')
+ })
+end
+
+# user_stream.rb
+#
+# to use,
+# > plug user_stream
+# > user_stream
+#
+# to stop,
+# > user_stream stop
+#
+# Specification
+# http://apiwiki.twitter.com/ChirpUserStreams
View
28 lib/plugins/whale.rb
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+module Termtter
+ class RubytterProxy
+ alias_method :error_html_message_orig, :error_html_message
+
+ def error_html_message_whale(e)
+ if %r'Twitter / Over capacity' =~ e.message
+ WHALE
+ else
+ error_html_message_orig(e)
+ end
+ end
+
+ alias_method :error_html_message, :error_html_message_whale
+ end
+
+ # first blank line is to skip prompt.
+ WHALE = <<'__WHALE__'
+
+  ,......-..-—‐—--..r、_   ,r:, Twitter / Over capacity
+ ヾー-゚、 :::::::::::::::::::::::::_ ̄ニ、く
+   ゙`ー-`'-ー'" ̄    `'
+__WHALE__
+end
+
+# whale.rb:
+# print whale when twitter is over capacity.
+# ASCII-Art from http://bhdaamov.hp.infoseek.co.jp/zukan/fish.html#kuji
View
6 lib/termtter.rb
@@ -14,16 +14,17 @@
require 'open-uri'
require 'optparse'
require 'readline'
-gem 'rubytter', '>= 0.11.0'
require 'rubytter'
require 'notify'
require 'timeout'
+require 'oauth'
module Termtter
VERSION = File.read(File.join(File.dirname(__FILE__), '../VERSION')).strip
APP_NAME = 'termtter'
require 'termtter/config'
+ require 'termtter/crypt'
require 'termtter/default_config'
require 'termtter/optparse'
require 'termtter/command'
@@ -43,4 +44,7 @@ module Termtter
CONF_DIR = File.expand_path('~/.termtter') unless defined? CONF_DIR
CONF_FILE = File.join(Termtter::CONF_DIR, 'config') unless defined? CONF_FILE
$:.unshift(CONF_DIR)
+
+ CONSUMER_KEY = 'eFFLaGJ3M0VMZExvNmtlNHJMVndsQQ=='
+ CONSUMER_SECRET = 'cW8xbW9JT3dyT0NHTmVaMWtGbHpjSk1tN0lReTlJYTl0N0trcW9Fdkhr'
end
View
10 lib/termtter/active_rubytter.rb
@@ -17,11 +17,7 @@ def [](key)
end
def method_missing(name, *args)
- if @data.key?(name)
- return @data[name]
- else
- super
- end
+ @data[name]
end
def attributes=(raw_hash)
@@ -44,6 +40,10 @@ def to_hash
end
end
+ def destructize
+ self.to_hash
+ end
+
def retweeted_status
nil
end
View
99 lib/termtter/api.rb
@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*-
-config.set_default(:host, 'twitter.com')
-if ENV.has_key?('HTTP_PROXY')
+config.set_default(:host, 'api.twitter.com')
+config.set_default(:oauth_consumer_site, 'http://api.twitter.com')
+if ENV['HTTP_PROXY'] || ENV['http_proxy']
require 'uri'
- proxy = ENV['HTTP_PROXY']
+ proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
proxy = "http://" + proxy if proxy !~ /^http:\/\//
u = URI.parse(proxy)
config.proxy.set_default(:host, u.host)
@@ -30,47 +31,71 @@ module API
class << self
attr_reader :connection, :twitter
def setup
- 3.times do
- begin
- if twitter = try_auth
- @twitter = twitter
- # NOTE: for compatible
- @connection = twitter.instance_variable_get(:@connection)
- break
- end
- rescue Timeout::Error
- puts TermColor.parse("<red>Time out :(</red>")
- exit!
+ # NOTE: for compatible
+ @connection = twitter.instance_variable_get(:@connection)
+ if config.access_token.empty? || config.access_token_secret.empty?
+ if config.token_file &&
+ File.exist?(File.expand_path(config.token_file))
+ config.access_token, config.access_token_secret = File.read(File.expand_path(config.token_file)) \
+ .split(/\r?\n/).map(&:chomp)
+ else
+ self.authorize_by_oauth(true)
end
end
- exit! unless twitter
+ access_token = OAuth::AccessToken.new(consumer, config.access_token, config.access_token_secret)
+ @twitter = RubytterProxy.new(access_token, twitter_option)
+
+ config.user_name = @twitter.verify_credentials[:screen_name]
end
+
+ def authorize_by_oauth(show_information=false, save_to_token_file=true, put_to_config=true, verbose=true)
+ puts '1. Contacting to twitter...' if verbose
+
+ request_token = consumer.get_request_token
+
+ puts '2. URL for authorize: ' + request_token.authorize_url if verbose
+ puts ' Opening web page to authorization...' if verbose
- def try_auth
- if config.user_name.empty? || config.password.empty?
- puts 'Please enter your Twitter login:'
+ begin
+ open_browser(request_token.authorize_url)
+ rescue BrowserNotFound
+ puts "Browser not found. Please log in and/or grant access to get PIN via your browser at #{request_token.authorize_url}"
end
+ sleep 2
ui = create_highline
+ pin = ui.ask('3. Enter PIN: ')
+ puts ""
+ puts "4. Getting access_token..."
+ access_token = request_token.get_access_token(:oauth_verifier => pin)
- if config.user_name.empty?
- config.user_name = ui.ask('Username: ')
- else
- puts "Username: #{config.user_name}"
- end
- if config.password.empty?
- config.password = ui.ask('Password: ') { |q| q.echo = false}
+ if put_to_config
+ config.access_token = access_token.token
+ config.access_token_secret = access_token.secret
end
- twitter = RubytterProxy.new(config.user_name, config.password, twitter_option)
- begin
- twitter.verify_credentials
- return twitter
- rescue Rubytter::APIError
- config.__clear__(:password)
+ if save_to_token_file
+ puts "5. Saving to token file... (" + config.token_file + ")"
+ open(File.expand_path(config.token_file),"w") do |f|
+ f.puts access_token.token
+ f.puts access_token.secret
+ end
end
- return nil
+
+ puts "Authorize is successfully done."
+
+ return {:token => access_token.token,
+ :secret => access_token.secret}
+ end
+
+ def consumer
+ @consumer ||= OAuth::Consumer.new(
+ Termtter::Crypt.decrypt(CONSUMER_KEY),
+ Termtter::Crypt.decrypt(CONSUMER_SECRET),
+ :site => config.oauth_consumer_site,
+ :proxy => proxy_string
+ )
end
def twitter_option
@@ -90,6 +115,16 @@ def twitter_option
:proxy_password => config.proxy.password
}
end
+
+ def proxy_string
+ return unless config.proxy.host
+ if config.proxy.user_name && config.proxy.password
+ "http://#{config.proxy.user_name}:#{config.proxy.password}@#{config.proxy.host}:#{config.proxy.port}"
+ else
+ "http://#{config.proxy.host}:#{config.proxy.port}"
+ end
+ end
+
end
end
end
View
32 lib/termtter/client.rb
@@ -17,9 +17,10 @@ module Client
Thread.abort_on_exception = true
class << self
- attr_reader :commands
+ attr_reader :commands, :logger, :task_manager
- # plug :: Name -> (Hash) -> IO () where NAME = String | Symbol | [NAME]
+ # call-seq:
+ # plug :: Name -> (Hash) -> IO () where NAME = String | Symbol | [NAME]
def plug(name, options = {})
if Array === name # Obviously `name.respond_to?(:each)` is better, but for 1.8.6 compatibility we cannot.
name.each {|i| plug(i, options) }
@@ -30,8 +31,9 @@ def plug(name, options = {})
return if config.system.disable_plugins.include?(name.gsub('defaults/', ''))
+ name_sym = name.gsub(/-/, '_').to_sym
options.each do |key, value|
- config.plugins.__refer__(name.gsub(/-/, '_').to_sym).__assign__(key.to_sym, value)
+ config.plugins.__refer__(name_sym).__assign__(key.to_sym, value)
end
load "plugins/#{name}.rb"
rescue Exception => e
@@ -42,6 +44,10 @@ def public_storage
@public_storage ||= {}
end
+ def memory_cache
+ @memory_cache ||= Termtter::MemoryCache.new
+ end
+
def add_filter(&b)
warn "add_filter method will be removed. Use Termtter::Client.register_hook(:name => ..., :point => :filter_for_output, :exec => ... ) instead."
@filters << b
@@ -68,6 +74,10 @@ def register_command(arg, opts = {}, &block)
@commands[command.name] = command
end
+ def remove_command(name)
+ commands.delete(name.to_sym)
+ end
+
def add_command(name)
if block_given?
command = Command.new(:name => name)
@@ -123,7 +133,7 @@ def output(statuses, event)
call_hooks(:post_filter, filtered, event)
get_hooks(:output).each do |hook|
- Termtter::Client.logger.debug "output: call hook :output #{hook.inspect}"
+ Termtter::Client.logger.debug { "output: call hook :output #{hook.inspect}" }
hook.call(
apply_filters_for_hook(:"filter_for_#{hook.name}", filtered, event),
event)
@@ -206,6 +216,10 @@ def add_task(*arg, &block)
@task_manager.add_task(*arg, &block)
end
+ def delete_task(name)
+ @task_manager.delete_task(name)
+ end
+
def exit
puts 'finalizing...'
call_hooks(:exit)
@@ -248,6 +262,7 @@ def logger
def setup_logger
@logger = config.logger || default_logger
@logger.level = config.devel ? Logger::DEBUG : Logger::INFO
+ call_hooks(:post_setup_logger)
@logger
end
@@ -303,12 +318,17 @@ def run
parse_options
config.__freeze__(:user_name) unless config.user_name.empty?
show_splash
- load_config
setup_task_manager
+ load_config
load_plugins
eval_init_block
config.__unfreeze__(:user_name)
- Termtter::API.setup
+ begin
+ Termtter::API.setup
+ rescue Rubytter::APIError => e
+ handle_error(e)
+ exit EXIT_FAILURE
+ end
config.system.eval_scripts.each do |script|
begin
View
33 lib/termtter/command.rb
@@ -28,7 +28,8 @@ def initialize(args)
set cfg
end
- # set :: Hash -> ()
+ # call-seq:
+ # set :: Hash -> ()
def set(cfg)
self.name = cfg[:name].to_sym
self.aliases = cfg[:aliases]
@@ -38,7 +39,8 @@ def set(cfg)
self.author = cfg[:author]
end
- # complement :: String -> [String]
+ # call-seq:
+ # complement :: String -> [String]
def complement(input)
input = input.sub(/^\s*/, '')
if match?(input) && input =~ /^[^\s]+\s/
@@ -53,7 +55,8 @@ def complement(input)
end
end
- # call :: ???
+ # call-seq:
+ # call :: ???
def call(cmd = nil, arg = nil, original_text = nil)
from = Time.now
arg = case arg
@@ -64,27 +67,30 @@ def call(cmd = nil, arg = nil, original_text = nil)
else
raise ArgumentError, 'arg should be String or nil'
end
- Termtter::Client.logger.debug "command: #{cmd} #{arg}"
+ Termtter::Client.logger.debug { "command: #{cmd} #{arg}" }
result = exec_proc.call(arg)
- Termtter::Client.logger.debug "command: #{cmd} #{arg} #{'%.2fsec' % (Time.now - from)