Skip to content

Instantly share code, notes, and snippets.

@melborne
Created February 7, 2010 12:29
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save melborne/297408 to your computer and use it in GitHub Desktop.
Save melborne/297408 to your computer and use it in GitHub Desktop.
Termtter plugins
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
# -*- 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
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
# -*- 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
# -*- 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
# -*- 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
#!/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
# -*- 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
#!/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