-
-
Save Epictetus/1102564 to your computer and use it in GitHub Desktop.
Termtter plugins
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Termtter::Client | |
# search replacement: | |
# ADD: #[page] arg for list next pages | |
register_command( | |
:name => :search, :aliases => [:s], | |
:exec_proc => lambda {|arg| | |
search_option = config.search.option.empty? ? {} : config.search.option | |
arg.gsub!(/\s*#(\d+)$/) { search_option[:page] = $1 ; ''} | |
if arg.empty? && tags = public_storage[:hashtags] | |
arg = tags.to_a.join(" ") | |
end | |
statuses = Termtter::API.twitter.search(arg, search_option) | |
public_storage[:search_keywords] << arg | |
output(statuses, SearchEvent.new(arg)) | |
}, | |
:completion_proc => lambda {|cmd, arg| | |
public_storage[:search_keywords].grep(/^#{Regexp.quote(arg)}/).map { |i| "#{cmd} #{i}" } | |
}, | |
:help => ["search,s TEXT [#PAGE]", "Search for Twitter"] | |
) | |
# CHG: hit word highlight format | |
config.plugins.search.set_default :highlight_format, '<on_magenta><white>\0</white></on_magenta>' | |
register_hook(:highlight_for_search_query, :point => :pre_coloring) do |text, event| | |
case event | |
when SearchEvent | |
query = event.query.split(/\s/).map {|q|Regexp.quote(q)}.join("|") | |
text.gsub(/(#{query})/i, config.plugins.search.highlight_format) #TODO: remain previous setting bug | |
else | |
text | |
end | |
end | |
# list command replacement | |
# ADD: #[page] arg for listing next pages | |
register_command( | |
:name => :list, :aliases => [:l], | |
:exec_proc => lambda {|arg| | |
options = {} | |
arg.gsub!(/\s*([-#])(\d+)/) do | |
case $1 | |
when '-' then options[:count] = $2 | |
when '#' then options[:page] = $2 | |
end | |
'' | |
end | |
last_error = nil | |
if arg.empty? | |
event = :list_friends_timeline | |
statuses = Termtter::API.twitter.home_timeline(options) | |
else | |
event = :list_user_timeline | |
statuses = [] | |
Array(arg.split).each do |user| | |
if user =~ /\/\w+/ | |
user_name, slug = *user.split('/') | |
user_name = config.user_name if user_name.empty? | |
user_name = normalize_as_user_name(user_name) | |
options[:per_page] = options[:count] | |
options.delete(:count) | |
statuses += Termtter::API.twitter.list_statuses(user_name, slug, options) | |
else | |
begin | |
if user =~ /^\d+$/ | |
profile = Termtter::API.twitter.user(nil, :screen_name => user) rescue nil | |
unless profile | |
status = Termtter::API.twitter.show(user) rescue nil | |
user = status.user.screen_name if status | |
end | |
end | |
user_name = normalize_as_user_name(user.sub(/\/$/, '')) | |
statuses += Termtter::API.twitter.user_timeline(user_name, options) | |
rescue Rubytter::APIError => e | |
last_error = e | |
end | |
end | |
end | |
end | |
output(statuses, event) | |
raise last_error if last_error | |
}, | |
:help => ["list,l [USERNAME]/[SLUG] [-COUNT] [#PAGE]", "List the posts"] | |
) | |
# uri-open replacement | |
# ADD: some arg for open some uris in browser | |
# CHG: accept ID without '$' at 'in [ID]' arg | |
config.plugins.uri_open.set_default :some, 5 | |
register_command( | |
:name => :'uri-open', :aliases => [:uo], | |
:exec_proc => lambda {|arg| | |
case arg.strip | |
when '' | |
open_uri public_storage[:uris].shift | |
when /^all$/ | |
public_storage[:uris]. | |
each {|uri| open_uri(uri) }. | |
clear | |
when /^some\s*(\d*)$/ | |
some = $1.empty? ? config.plugins.uri_open.some : $1.to_i | |
some.times do | |
next unless uri = public_storage[:uris].shift | |
open_uri(uri) | |
end | |
when /^list$/ | |
public_storage[:uris]. | |
enum_for(:each_with_index). | |
to_a. | |
reverse. | |
each do |uri, index| | |
puts "#{index}: #{uri}" | |
end | |
when /^delete\s+(\d+)$/ | |
puts 'delete' | |
public_storage[:uris].delete_at($1.to_i) | |
when /^clear$/ | |
public_storage[:uris].clear | |
puts "clear uris" | |
when /^in\s+(.*)$/ | |
$1.split(/\s+/).each do |id| | |
id = Termtter::Client.typable_id_to_data(id) unless id =~ /\d+/ | |
if s = Termtter::API.twitter.show(id) rescue nil | |
URI.extract(s.text, PROTOCOLS).each do |uri| | |
open_uri(uri) | |
public_storage[:uris].delete(uri) | |
end | |
end | |
end | |
when /^(\d+)$/ | |
open_uri(public_storage[:uris].at($1.to_i)) | |
else | |
puts "**parse error in uri-open**" | |
end | |
}, | |
:completion_proc => lambda {|cmd, arg| | |
%w(all list delete clear in some).grep(/^#{Regexp.quote arg}/).map {|a| "#{cmd} #{a}" } | |
} | |
) | |
register_command( | |
:name => :more, | |
:exec_proc => lambda {|arg| | |
break if Readline::HISTORY.length < 2 | |
i = Readline::HISTORY.length - 2 | |
input = "" | |
cnt = 0 | |
begin | |
input = Readline::HISTORY[i] | |
i -= 1 | |
cnt += 1 | |
return if i <= 0 | |
end while input == "more" or input =~ /^(some|o|uri-open|uo|[0-7])/ | |
begin | |
if input =~ /^(l|list|s|search|user search)(\s+|$)/ | |
input.slice!(/\s*#(\d+)/) | |
cnt += $1.nil? ? 1 : $1.to_i | |
Termtter::Client.execute(input + " ##{cnt}") | |
end | |
if input =~ /^(google_web|google|gs | |
|google_blog|gb | |
|google_book|gbk | |
|google_image|gi | |
|google_video|gv | |
|google_news|gn | |
|google_patent|gp | |
|google_next_page|gnext)(\s+|$)/x | |
Termtter::Client.execute("google_next_page") | |
end | |
rescue CommandNotFound => e | |
warn "Unknown command \"#{e}\"" | |
warn 'Enter "help" for instructions' | |
rescue => e | |
handle_error e | |
end | |
}, | |
:help => ["more", "List next results"] | |
) | |
# easy_post plugin replacement with confirm | |
module Termtter::Client | |
register_hook(:easy_post, :point => :command_not_found) do |text| | |
if config.confirm && text.length > 15 | |
execute("update #{text}") | |
else | |
raise Termtter::CommandNotFound, text | |
end | |
end | |
end | |
# plugin plugin fix bug | |
register_command( | |
:name => :plug, | |
:alias => :plugin, | |
:exec_proc => lambda {|arg| | |
if arg.empty? | |
puts plugin_list.join(', ') | |
return | |
end | |
begin | |
result = plug arg | |
rescue LoadError | |
ensure | |
puts "=> #{result.inspect}" | |
end | |
}, | |
:completion_proc => lambda {|cmd, args| | |
plugin_list.grep(/#{Regexp.quote(args)}/).map {|i| "#{cmd} #{i}"} | |
}, | |
:help => ['plug FILE', 'Load a plugin'] | |
) | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding:utf-8 -*- | |
require "twitter_search" | |
module Termtter::Client | |
register_command( | |
:name => :fuzzy_find, | |
:alias => :ff, | |
:help => ['fuzzy_find,ff QUERY', 'Twitter User Fuzzy Search'], | |
:exec => lambda do |query| | |
opts = {:size => 10, :verbose => false} | |
query.gsub!(/(-l)\s*(\d+)/) { opts[:size] = $2.to_i; '' } | |
query.gsub!(/-v/) { opts[:verbose] = true; ''} | |
public_storage[:uris].clear | |
pf = TwitterSearch::Fuzzy.new(query, opts[:size]) | |
unless opts[:verbose] | |
pf.users.each_with_index do |(name, uri), i| | |
print "<green>#{i+1}:#{name}</green> => #{uri}\n".termcolor | |
public_storage[:uris] << uri | |
end | |
else | |
pf.user_profiles.each_with_index do |(name, profile), i| | |
puts "<green>#{i+1}:#{name}</green>".termcolor | |
print profile.delete_if{|k,v| k == :fn}. | |
map { |k, v| " <red>#{k}</red> => #{v}" }.join("\n").termcolor + "\n" | |
public_storage[:uris] << profile[:url] | |
end | |
end | |
opts[:size].times { |n| register_alias("#{n+1}", "uo #{n}") } | |
end | |
) | |
end | |
# find Twitter users with query | |
# usage: ff termtter -l30 (find top 30 termtterer) | |
# ff ruby -v (find ruby lovers in vebose mode) | |
# to open urls on list, use uri-open all or just hit a number | |
# require twitter_search library and uri-open plugin | |
# more info => hp12c http://d.hatena.ne.jp/keyesberry/20100610/p1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require "net/imap" | |
require "kconv" | |
require "termcolor" | |
class Gmail | |
def initialize(username, password) | |
begin | |
@imap = Net::IMAP.new('imap.gmail.com', 993, true, nil, false) | |
@imap.login(username, password) | |
rescue Exception => e | |
puts e | |
exit | |
end | |
end | |
def fetch(select="INBOX", ids="UNSEEN") | |
begin | |
puts "fetching gmail messages..." | |
@imap.examine(select) | |
ids = @imap.search(ids) | |
puts "you have <red>#{ids.length}</red> unread messages.".termcolor | |
return if ids.length < 1 | |
@imap.fetch(ids, "ENVELOPE").each_with_index do |mail, i| | |
sender = mail.attr["ENVELOPE"].sender[0] | |
name = sender.name || sender.mailbox || sender.host | |
subject = mail.attr["ENVELOPE"].subject || "(no subject)" | |
puts "<90>#{i+1}:</90><green>#{name.toutf8} : </green>".termcolor + | |
TermColor.colorize("#{subject.toutf8}", 'yellow') | |
end | |
rescue Exception => e | |
puts e | |
ensure | |
@imap.disconnect | |
end | |
end | |
end | |
module Termtter::Client | |
register_command( | |
:name => :gmail, :alias => :gm, | |
:help => ["gmail,gm", "Just check unread gmail messages"], | |
:exec_proc => lambda { |arg| | |
username = config.plugins.gmail.username | |
password = config.plugins.gmail.password | |
if username.empty? | |
username = create_highline.ask('Username: ') | |
end | |
if password.empty? | |
password = create_highline.ask('Password: ') { |q| q.echo = false } | |
end | |
Gmail.new(username, password).fetch | |
} | |
) | |
register_command( | |
:name => :gmail_open, :alias => :gmo, | |
:help => ["gmail_open,gmo", "Open gmail with your browser"], | |
:exec_proc => lambda { |arg| open_uri "https://mail.google.com"} | |
) | |
end | |
# fetch titles from unread gmail messages | |
# usage: hit 'gmail' command without arg, then name and password will be asked | |
# you can set them at .termtter/config as follows; | |
# config.plugins.gmail.username = 'username' | |
# config.plugins.gmail.password = 'password' | |
# more info => :hp12c http://d.hatena.ne.jp/keyesberry/20100221/p1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding: utf-8 -*- | |
require "google-search" | |
module Google | |
class Search | |
def self.url_encode string | |
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) { | |
'%' + $1.unpack('H*')[0].scan(/../).join('%').upcase | |
}.tr(' ', '+') | |
end | |
end | |
end | |
module Termtter::Client | |
# for All Searches | |
# :verbose = :true, :false | |
# :colors = ex. ['green', 'on_yellow', 'underline'] | |
# :lang = ex. :ja, :en, :cn, :fr.. | |
# :page_size = :small, :large (4 : 8) | |
# :site = ex. 'ja.wikipedia.org' | |
config.plugins.google.set_default :verbose, false | |
config.plugins.google.set_default :colors, ['green'] | |
config.plugins.google.set_default :lang, :ja | |
config.plugins.google.set_default :page_size, :large | |
config.plugins.google.set_default :site, nil | |
# for Google News | |
# :news_edition = ex. :jp, :us, :uk, :fr_ca.. | |
# :news_topic = :headlines, :world, :business, :nation, :science, | |
# :elections, :politics, :entertainment, :sports, :health | |
# :news_relative_to = ex. city, state, province, zipcode.. | |
config.plugins.google.set_default :news_edition, :jp | |
config.plugins.google.set_default :news_topic, :headlines | |
config.plugins.google.set_default :news_relative_to, nil | |
# for Google Image | |
# :image_size = :icon, :small, :medium, :large, :xlarge, :xxlarge, :huge | |
# :image_type = :face, :photo, :clipart, :lineart | |
# :file_type = :jpg, :png, :gif, :bmp | |
config.plugins.google.set_default :image_color, nil | |
config.plugins.google.set_default :image_size, nil | |
config.plugins.google.set_default :image_type, nil | |
config.plugins.google.set_default :file_type, nil | |
# for Google Patent | |
config.plugins.google.set_default :patent_issued_only, false | |
public_storage[:google] = nil | |
public_storage[:google_verbose] = nil | |
class << self | |
def print_search_result(search, verbose) | |
search.response.reverse_each.with_index do |res, i| | |
public_storage[:uris].unshift res.uri | |
puts colorize("#{search.response.items.length-1-i}: #{res.title}") + | |
" <underline>#{res.uri}</underline>".termcolor | |
puts "\t#{res.content}".gsub(/\<b\>\w+\<\/b\>/, '<red>\0</red>').termcolor if verbose | |
end | |
public_storage[:google] = search | |
public_storage[:google_verbose] = verbose | |
end | |
def colorize(str) | |
config.plugins.google.colors.each { |c| str = TermColor.colorize(str, c) } | |
str | |
end | |
end | |
GOOGLE_SEARCHES = { | |
:google_web => [ [:google, :gs], ['google_web,google,gs [-lvp VALUE] [--site] QUERY', 'Google Web Search']], | |
:google_blog => [ [:gb], ['google_blog,gb [-lvp VALUE] [--site] QUERY', 'Google Blog Search']], | |
:google_book => [ [:gbk],['google_book,gbk [-lp VALUE] QUERY', 'Google Book Search']], | |
:google_image => [ [:gi], ['google_image,gi [-ctfslvp VALUE] [--site] QUERY', 'Google Image Search']], | |
:google_video => [ [:gv], ['google_video,gv [-lvp VALUE] [--site] QUERY', 'Google Video Search']], | |
:google_news => [ [:gn], ['google_news,gn [-etrvp VALUE] QUERY', 'Google News Search']], | |
:google_patent => [ [:gp], ['google_patent,gp [-ivp VALUE] QUERY', 'Google Patent Search']] | |
} | |
GOOGLE_SEARCHES.each do |name, attrs| | |
register_command( | |
:name => name, :aliases => attrs[0], :help => attrs[1], | |
:exec => lambda do |query| | |
opts = {} | |
target = name.to_s.sub(/^\w+_/,'').capitalize | |
search = instance_eval("Google::Search::#{target}").new do |s| | |
if config.plugins.google.site && query.sub!(/--site/, '') | |
query += " site:#{URI.escape(config.plugins.google.site)}" | |
end | |
query.gsub!(/-(l|p|v)\s+:*(\w+)\s*/) { opts[$1.to_sym] = $2.to_sym ; nil } | |
s.query = query | |
s.language = opts[:l] || config.plugins.google.lang | |
s.size = opts[:p] || config.plugins.google.page_size | |
case target | |
when 'News' | |
query.gsub!(/-(e|t|r)\s+:*(\w+)\s*/) { opts[$1.to_sym] = $2.to_sym ; nil } | |
s.edition = opts[:e] || config.plugins.google.news_edition | |
s.topic = opts[:t] || config.plugins.google.news_topic | |
s.relative_to = opts[:r] || config.plugins.google.news_relative_to | |
when 'Image' | |
query.gsub!(/-(c|s|t|f)\s+:*(\w+)\s*/) { opts[$1.to_sym] = $2.to_sym ; nil } | |
s.color = opts[:c] || config.plugins.google.image_color | |
s.image_size = opts[:s] || config.plugins.google.image_size | |
s.image_type = opts[:t] || config.plugins.google.image_type | |
s.file_type = opts[:f] || config.plugins.google.file_type | |
when 'Patent' | |
query.gsub!(/-(i)\s+:*(\w+)\s*/) { opts[$1.to_sym] = $2.to_sym ; nil } | |
s.issued_only = opts[:i] || config.plugins.google.patent_issued_only | |
end | |
end | |
mode = opts[:v] || config.plugins.google.verbose | |
print_search_result(search, mode) | |
end | |
) | |
end | |
register_command( | |
:name => :google_next_page, :alias => :gnext, | |
:help => ['google_next_page,gnext', 'List Next Google Search Results'], | |
:exec_proc => lambda { |arg| | |
begin | |
search = public_storage[:google].next | |
verbose = public_storage[:google_verbose] || false | |
print_search_result(search, verbose) | |
rescue | |
end | |
} | |
) | |
nums = config.plugins.google.page_size == :small ? 4 : 8 | |
nums.times { |n| register_alias("#{n}", "uo #{n}") } | |
end | |
# Google Search functionality include; Web, Blog, Book, Image, Video, News, Patent | |
# see comments in above code for the options | |
# require google-search library and uri-open plugin | |
# more info http://d.hatena.ne.jp/keyesberry/20100212/p1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
require 'appscript' or raise 'itunes plugin cannot run' | |
config.plugins.itunes.set_default(:prefix, 'Listening now:') | |
config.plugins.itunes.set_default(:suffix, '#iTunes #nowplaying') | |
config.plugins.itunes.set_default( | |
:format, | |
'<%=prefix%> <%=track_name%> (<%=time%>) <%=artist%> <%=album%> <%=uri%> <%=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 | |
uri = "http://www.last.fm/music/#{artist.split(' ').join('+')}/_/#{track_name.split(' ').join('+')}" | |
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 | |
end | |
} | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- encoding: utf-8 -*- | |
$:.unshift(File.dirname(__FILE__)) | |
require 'yahoo_stock' | |
require 'yahoojp_stock' | |
class Numeric | |
def yen | |
i, f = self.to_s.split('.') | |
i = i.reverse.scan(/\d{1,3}/).join(',').reverse | |
i = "-#{i}" if self < 0 | |
f ? "#{i}.#{f}" : i | |
end | |
end | |
module Termtter::Client | |
class << self | |
def print_find_stock(header, values, format) | |
printf "<green>#{format}</green>".termcolor + "\n", *header | |
values.each do |value| | |
next if value.length < 6 #remove error data | |
value.delete_at(2) if value.length > 6 #remove error data | |
printf "<yellow>#{format}</yellow>".termcolor + "\n", *value | |
end | |
end | |
end | |
register_command( | |
:name => :stock_find, :alias => :stf, | |
:help => ['stock_find,stf COMPANY_NAME', 'Find Stock Symbol'], | |
:exec => lambda do |name| | |
begin | |
case name | |
when /^[A-Z]/ | |
res = YahooStock::ScripSymbol.new(name) | |
header = res.data_attributes | |
values = res.results(:to_array).output | |
header[-1], header[-2] = header[-2], header[-1] | |
values.each { |val| val[-1], val[-2] = val[-2], val[-1] } | |
format = "%-12s%-30s%-11s%-7s%-9s%-30s" | |
else | |
header, *values = YahooJPStock::Find.new(name).output | |
format = "%-8s%-12s%-40s%-15s%-15s%-15s" | |
end | |
print_find_stock(header, values, format) | |
rescue | |
puts 'Stock Not Found' | |
end | |
end | |
) | |
class << self | |
def print_stock_price(data) | |
data.each do |quote| | |
quote = quote.transpose | |
name, symbol = quote.shift(2) | |
printf "<red>- %s[%s] -</red>\n".termcolor, name[1], symbol[1] | |
quote.each do |name, val| | |
color = val =~ /^-\d/ ? 'red' : 'yellow' | |
print "<green>#{name} :</green> <#{color}>#{val}</#{color}> ".termcolor | |
end | |
print "\n" | |
end | |
end | |
end | |
register_command( | |
:name => :stock_price, :alias => :stp, | |
:help => ['stock_price,stp [-r|s] SYMBOLS', 'Show Stock Price Data'], | |
:exec => lambda do |symbols| | |
case symbols.to_i | |
when 0 | |
opt = :standard | |
symbols.sub!(/\s*-(r|s)\s*/) { opt = $1 == 'r' ? :realtime : :standard; nil } | |
quotes = YahooStock::Quote.new(:stock_symbols => symbols.split(/\s+/)) | |
quotes.send(opt) | |
output = quotes.results(:to_hash).output | |
output.map! do |q| | |
q.delete(:ticker_trend) | |
name, symbol = q.delete(:name), q.delete(:symbol) | |
q.to_a.unshift([:name, name], [:symbol, symbol]).transpose | |
end | |
else | |
output = symbols.split(/\s+/).map { |symbol| YahooJPStock::Quote.new(symbol).output(:to_array) } | |
end | |
print_stock_price(output) | |
end | |
) | |
class << self | |
def print_stock_history(titles, values) | |
printf "<green>%10s</green>".termcolor * titles.length + "\n", *titles | |
values.reverse_each do |val| | |
date, *val = val | |
date.gsub!(/(\D)(\d)(?=\D)/) { $1 + '0' + $2 } | |
printf "<red>%10s</red>".termcolor + | |
"<yellow>%10s</yellow>".termcolor * val.length + "\n", date, *val | |
end | |
end | |
end | |
register_command( | |
:name => :stock_history, :alias => :sth, | |
:help => ['stock_history,sth SYMBOL [FROM] [TO]', 'Show Stock History Data'], | |
:exec => lambda do |arg| | |
begin | |
symbol = arg[/^(\w+|\d+)/] | |
from, to = arg.scan(/\d{4}[-\/]\d{1,2}[-\/]\d{1,2}/) | |
term = arg[/:(daily|weekly|monthly)/] | |
start_date = Date.parse(from) rescue Date.today-10 | |
end_date = Date.parse(to) rescue Date.today-1 | |
term = term ? term : :daily | |
case symbol.to_i | |
when 0 | |
titles, *values = | |
YahooStock::History.new(:stock_symbol => symbol, | |
:start_date => start_date, | |
:end_date => end_date | |
).values_with_header.split("\n").map { |line| line.split(",") } | |
else | |
titles, *values = YahooJPStock::History.new(symbol, start_date, end_date, term).output | |
end | |
print_stock_history(titles, values) | |
rescue | |
puts 'Stock Not Found or Date Range Not Good' | |
end | |
end | |
) | |
class << self | |
def stock_prices(stock) | |
current_price = stock.current_price[1].gsub(/\D+/, '').to_i | |
day_change = stock.day_change[1].tr('()', '()') | |
last_trade_price = stock.last_trade_price[1].gsub(',', '')[/\d+/].to_i | |
return current_price, day_change, last_trade_price | |
end | |
def print_portfolios(q, print_data) | |
printf "<red>%s[%s]</red>\n".termcolor, q.name[1], q.symbol[1] | |
print_data.each do |title, value| | |
color = value =~ /^-\d/ ? 'red' : 'yellow' | |
printf " <green>%s: </green><#{color}>%s</#{color}>".termcolor, title, value | |
end | |
print "\n" | |
end | |
def print_indices(indices) | |
indices.each do |name, value| | |
q = YahooJPStock::Quote.new(value) | |
printf "<red>%s: </red>".termcolor, "#{name}" | |
printf "<green>%s</green> <yellow>%s</yellow> ".termcolor * 2, | |
q.current_price[0], q.current_price[1], q.day_change[0], q.day_change[1] | |
end | |
print "\n" | |
end | |
def print_total(total_value, total_profit, total_pratio, total_change, total_cratio) | |
print "<red>Portfolio Value</red>\n".termcolor | |
printf " <green>%s: </green><yellow>%s</yellow> ".termcolor, | |
'評価額', total_value.yen | |
color = total_profit.yen =~ /^-\d/ ? 'red' : 'yellow' | |
printf "<green>%s: </green><#{color}>%s(%+.2f%%)</#{color}> ".termcolor, | |
'含み損益', total_profit.yen, total_pratio | |
color = total_change.yen =~ /^-\d/ ? 'red' : 'yellow' | |
printf "<green>%s: </green><#{color}>%s(%+.2f%%)</#{color}>\n".termcolor, | |
'前日比', total_change.yen, total_cratio | |
end | |
end | |
register_command( | |
:name => :stock_portfolio, :alias => :stpo, | |
:help => ['stock_portfolio,stpo', 'Show Your Portfolio Current Value'], | |
:exec => lambda do |arg| | |
begin | |
indices = [[:日経平均, '998407'], [:Topix, '998405']] | |
print_indices(indices) | |
portfolios = config.plugins.stock | |
total_value, total_profit, total_cost, total_last_value = 0, 0, 0, 0 | |
portfolios.each do |code, vol, buy| | |
q = YahooJPStock::Quote.new(code) | |
current_price, day_change, last_trade_price = stock_prices(q) | |
current_value = current_price * vol | |
cost = buy.to_f * vol | |
profit = current_value - cost | |
pratio = (profit / cost * 100.00).to_s[/.*\.\d{2}/] | |
print_data = [['現在値', current_price.yen], ['前日比', day_change], | |
['損益', "#{profit.yen}(#{pratio}%)"], ['評価額', current_value.yen], | |
['買値', buy.to_f.yen], ['数量', vol.yen] ] | |
print_portfolios(q, print_data) | |
total_value += current_value | |
total_last_value += last_trade_price * vol | |
total_cost += cost | |
total_profit += profit | |
end | |
total_pratio = total_profit / total_cost * 100.00 | |
total_change = total_value - total_last_value | |
total_cratio = 100.00 * total_change / total_value | |
print_total(total_value, total_profit, total_pratio, total_change, total_cratio) | |
rescue => e | |
puts "Error: " + e | |
puts "setup your data at .termtter/config?" | |
puts " ex. config.plugins.stock = [['4689.t', 1000, 28000], ['7203.t', 3500, 6520.30]]" | |
end | |
end | |
) | |
end | |
# Company Stock Infomation Fetcher with Yahoo Finance and Yahoo Finance Japan | |
# Includes: price data, symbol finder, history data and portfolio valuation | |
# require yahoo_stock and yahoojp_stock libraries | |
# more info :http://d.hatena.ne.jp/keyesberry/20100219/p1 | |
# http://d.hatena.ne.jp/keyesberry/20100302/p1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/ruby | |
# -*- encoding:utf-8 -*- | |
require "google-search" | |
require "nokogiri" | |
module Google | |
class Search | |
def self.url_encode string | |
string.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/) { | |
'%' + $1.unpack('H*')[0].scan(/../).join('%').upcase | |
}.tr(' ', '+') | |
end | |
end | |
end | |
module TwitterSearch | |
BASE_URLS = { | |
:twitter => "http://twitter.com/" | |
} | |
class InterfaceError < RuntimeError ; end | |
class SearchError < RuntimeError ; end | |
class ParseError < RuntimeError ; end | |
module Interface | |
require "net/http" | |
def get(uri) | |
Net::HTTP.get_response(URI.parse(uri)) | |
rescue => e | |
#raise InterfaceError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
end | |
class Fuzzy | |
include Interface | |
attr_reader :users | |
def initialize(query, size=10) | |
@users = user_search(query, size) | |
end | |
def user_search(query, size) | |
search = googling_twitter(query) | |
users, cnt, limit = {}, 0, 10 | |
limit.times do | |
search_results = cnt.zero? ? search.response : search.next.response | |
search_results.each do |res| | |
if res.title =~ /\son\sTwitter/ && !users[(title=res.title.sub($&,''))] | |
users[title] = res.uri | |
cnt += 1 | |
end | |
return users if cnt >= size | |
end | |
end | |
users | |
end | |
def googling_twitter(query) | |
Google::Search::Web.new do |s| | |
query += " site:#{BASE_URLS[:twitter]}" | |
s.query = query | |
end | |
rescue => e | |
raise SearchError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
def user_profiles | |
threads, result_hash = [], {} | |
@users.each do |result| | |
threads << Thread.new(result) do |name, uri| | |
next unless url = get(uri) | |
if block_given? | |
yield name, parse_profile(url) | |
else | |
result_hash[name] = parse_profile(url) | |
end | |
end | |
end | |
threads.each { |th| th.join } | |
result_hash | |
end | |
def parse_profile(response) | |
profile_css = "div#profile li" | |
profile = {} | |
case response | |
when Net::HTTPSuccess | |
parsed_body = Nokogiri::HTML(response.body) | |
parsed_body.css(profile_css).each do |node| | |
profile_tree = node.children.last | |
if v = profile_tree.attributes['class'] | |
key = v.value.to_sym | |
else | |
next #irregular case | |
end | |
profile[key] = | |
key == :url ? profile_tree.attributes['href'].value : profile_tree.text | |
end | |
end | |
profile | |
rescue => e | |
raise ParseError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
end | |
end | |
if __FILE__ == $0 | |
pf = TwitterSearch::Fuzzy.new('ruby hacker', 15) | |
pf.user_profiles do |name, profile| | |
print "#{name} => #{profile}\n" | |
end | |
end | |
# twitter_search library for fuzzy_find plugin | |
# see description for fuzzy_find.rb | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding:utf-8 -*- | |
if RUBY_VERSION < '1.9.0' | |
$KCODE = 'u' | |
require "jcode" | |
end | |
class String | |
def mirror(opt) | |
reversed = RUBY_VERSION < '1.9.0' ? self.split(//).reverse.join : self.reverse | |
opt.empty? ? self.replace(reversed) : self.replace(self.chop + reversed) | |
end | |
def rot13(opt=nil) | |
from = 'A-Ma-mN-Zn-zあ-なア-ナに-んニ-ン' | |
to = 'N-Zn-zA-Ma-mに-んニ-ンあ-なア-ナ' | |
if RUBY_VERSION >= '1.9.0' | |
from += '一-盒盓-龥' | |
to += '盓-龥一-盒' | |
end | |
self.tr(from, to) | |
end | |
def scooch(opt) | |
opt = opt.to_i.zero? ? 1 : opt.to_i | |
self.split(//).map { |c| opt.times { c = c.next }; c }.join | |
end | |
end | |
module Termtter::Client | |
uglies = { | |
:update_mirror => [[:um], :mirror, 'Mirror message'], | |
:update_rot13 => [[:u13], :rot13, 'Rot13 message'], | |
:update_scooch => [[:us], :scooch, 'Scooch message'], | |
:update_crypt => [[:uc], :crypt, 'Crypt message'] | |
} | |
uglies.each do |name, (aliases, meth, help)| | |
register_command( | |
:name => name, :aliases => aliases, | |
:exec_proc => lambda {|arg| | |
opt = '' | |
arg.sub!(/^-\s*([\d\w]+)\s+/) { opt = $1; '' } | |
text = "#{arg.send(meth, opt)} ##{meth.to_s}message" | |
text = text + "#{opt}" if [:update_crypt, :update_scooch].include? name | |
Termtter::API::twitter.update(text) | |
puts "=> #{text}" | |
}, | |
:help => ["#{name},#{aliases.join(',')} [-VALUE] TEXT", help] | |
) | |
end | |
end | |
# some modificators for tweet | |
# more info => http://d.hatena.ne.jp/keyesberry/20100216/p1 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/local/bin/ruby | |
# -*- encoding:utf-8 -*- | |
require "nokogiri" | |
require "date" | |
class String | |
def force_encode(encode) | |
if RUBY_VERSION < '1.9.0' | |
require 'kconv' | |
toutf8 | |
else | |
force_encoding(encode) | |
end | |
end | |
end | |
module YahooJPStock | |
BASE_URLS = { | |
:quote => "http://stocks.finance.yahoo.co.jp/stocks/detail/", | |
:find => "http://stocks.finance.yahoo.co.jp/stocks/search/", | |
:history => "http://table.yahoo.co.jp/" | |
} | |
module Interface | |
require "net/http" | |
def get(uri) | |
Net::HTTP.get_response(URI.parse(uri)) | |
rescue => e | |
raise InterfaceError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
end | |
class Find | |
include Interface | |
def initialize(company_name) | |
@company_name = URI.escape(company_name) | |
@candidates = [] | |
uri = BASE_URLS[:find] + "?s=" + @company_name | |
parse get(uri) | |
end | |
def parse(response) | |
case response | |
when Net::HTTPSuccess | |
parsed_html = Nokogiri::HTML(response.body) | |
@company_name = parsed_html.css('title').text.sub(/:.*/, '') | |
parsed_html.css('div.boardFinList tr').each do |tr| | |
@candidates << tr.search('td', 'th').inject([]) { |mem, item| mem << item.text; mem } | |
end | |
@candidates.map! do |ca| | |
if ca.length > 8 | |
ca[5,2] = "#{ca[5]}(#{ca[6]})" | |
ca[3,2] = "#{ca[4]}(#{ca[3]})" | |
end | |
ca[0..-2] | |
end | |
when Net::HTTPRedirection | |
code = response['location'].match(/\?code=/).post_match | |
q = Quote.new(code) | |
2.times do |i| | |
@candidates << [q.symbol[i], q.exchange[i], q.name[i], q.current_price[i], q.last_trade_price[i], q.volume[i]] | |
end | |
@candidates | |
end | |
rescue => e | |
raise ParseError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
def output | |
@candidates | |
end | |
end | |
class Quote | |
include Interface | |
SUMMARY = { | |
:exchange => ['div.selectFin span.s170', 'div.selectFin option'], | |
:current_price => ['div.priceDetail td.yjSt', 'div.priceDetail span.yjFL'], | |
:day_change => ['div.priceDetail span.yjMSt', 'div.priceDetail p.yjSt'] | |
} | |
DETAILS = [ | |
:last_trade_price, :open_price, :day_high, :day_low, :volume, | |
:trade_amount, :day_range, :market_cap, :shares, :div_yield, | |
:dividend, :per, :pbr, :eps, :bps, :minimum_cost, :minimum_shares, | |
:year_high, :year_low, :outstand_margin_buy, :margin_buy_week_change, | |
:oustand_margin_sell, :margin_sell_week_change | |
] | |
(SUMMARY.keys + DETAILS).each { |k| attr_reader k } | |
attr_reader :name, :symbol | |
def initialize(stock_code) | |
@symbol = ['コード', URI.escape(stock_code)] | |
@name = ['名称', nil] | |
uri = BASE_URLS[:quote] + "?code=" + @symbol[1] | |
parse get(uri) | |
end | |
def parse(response) | |
case response | |
when Net::HTTPSuccess | |
parsed_html = Nokogiri::HTML(response.body) | |
@name[1], @symbol[1] = parsed_html.css('title').text.scan(/^(.+)【(\d+)】/).flatten | |
SUMMARY.each do |k, (n, v)| | |
title, value = | |
if name_value = parsed_html.at_css(n) | |
name_value.text.sub(/\n.*/, '').split(':') | |
else | |
[k, nil] | |
end | |
value = parsed_html.at_css(v).text.sub(/\n/, '') if value.nil? and parsed_html.at_css(v) | |
instance_variable_set("@#{k.to_s}", [title, value]) | |
end | |
parsed_html.css('div.lineFi').each_with_index do |node, i| | |
title = node.search('dt').text.sub(/\n.*/, '') | |
value = node.search('dd').text.gsub(/\n/, '') | |
instance_variable_set("@#{DETAILS[i].to_s}", [title, value]) | |
end | |
end | |
rescue => e | |
raise ParseError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
def output(format=:to_hash) | |
case format | |
when :to_hash | |
(SUMMARY.keys + DETAILS).inject({}) do |mem, meth| | |
mem[meth] = send(meth) | |
mem | |
end.merge({:name => name, :symbol => symbol}) | |
when :to_array | |
(SUMMARY.keys + DETAILS).inject([]) do |mem, meth| | |
mem += [send(meth)] if send(meth) | |
mem | |
end.unshift(name, symbol).transpose | |
end | |
end | |
end | |
class History | |
include Interface | |
def initialize(stock_code, start_date, end_date, term=:daily) #term= :daily, :weekly, :monthly | |
@symbol = URI.escape(stock_code) | |
st = start_date.respond_to?(:year) ? start_date : Date.parse(start_date) | |
en = end_date.respond_to?(:year) ? end_date : Date.parse(end_date) | |
term = term.to_s[/\w/] | |
uri = BASE_URLS[:history] + "t?" + "c=#{st.year}&a=#{st.mon}&b=#{st.day}" + | |
"&f=#{en.year}&d=#{en.mon}&e=#{en.day}" + | |
"&g=#{term}&s=#{@symbol}&y=0&z=#{@symbol}&x=sb" | |
@histories = [] | |
parse get(uri) | |
end | |
def parse(response) | |
case response | |
when Net::HTTPSuccess | |
parsed_html = Nokogiri::HTML(response.body.force_encode('EUC-JP')) | |
parsed_html.css("table > tr").each do |tr| | |
bg = tr.attributes['bgcolor'] | |
next unless bg && ["#eeeeee", "#ffffff"].include?(bg.value) | |
@histories << | |
tr.search('td small', 'th small').inject([]) { |mem, item| mem << item.text; mem } | |
end | |
end | |
rescue => e | |
raise ParseError, "#{e.message}\n\n#{e.backtrace}" | |
end | |
def output | |
@histories | |
end | |
end | |
end | |
if __FILE__ == $0 | |
p YahooJPStock::Quote.new('998405').output(:to_array) | |
#p YahooJPStock::Find.new('toyota').output | |
#p YahooJPStock::History.new('7203', '2010/1/10', '2010/2/10').output | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment