facy 1.2.9.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +34 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +32 -0
- data/Rakefile +1 -0
- data/bin/facy +18 -0
- data/config.yml +4 -0
- data/facy.gemspec +27 -0
- data/lib/facy.rb +32 -0
- data/lib/facy/command.rb +180 -0
- data/lib/facy/converter.rb +91 -0
- data/lib/facy/core.rb +104 -0
- data/lib/facy/core_ext.rb +9 -0
- data/lib/facy/exception.rb +3 -0
- data/lib/facy/facebook.rb +108 -0
- data/lib/facy/get_token.rb +112 -0
- data/lib/facy/help.rb +28 -0
- data/lib/facy/image_viewer.rb +82 -0
- data/lib/facy/input_queue.rb +35 -0
- data/lib/facy/logger.rb +26 -0
- data/lib/facy/option_parser.rb +23 -0
- data/lib/facy/output.rb +183 -0
- data/lib/facy/version.rb +3 -0
- metadata +153 -0
data/lib/facy/core.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Facy
|
2
|
+
module Core
|
3
|
+
def config
|
4
|
+
@config ||= {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def inits
|
8
|
+
@inits ||= []
|
9
|
+
end
|
10
|
+
|
11
|
+
def init(&block)
|
12
|
+
inits << block
|
13
|
+
end
|
14
|
+
|
15
|
+
def _init
|
16
|
+
load_config
|
17
|
+
login_flow
|
18
|
+
inits.each { |block| class_eval(&block) }
|
19
|
+
log(:info, "core module init success")
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_config
|
23
|
+
config.reverse_update(default_config)
|
24
|
+
log(:info, "config loaded #{config.to_s}")
|
25
|
+
end
|
26
|
+
|
27
|
+
def default_config
|
28
|
+
config = YAML.load_file(File.expand_path("../../../config.yml", __FILE__))
|
29
|
+
{
|
30
|
+
session_file_folder: "/tmp",
|
31
|
+
session_file_name: ".facy_access_token.yml",
|
32
|
+
log_folder: "/tmp",
|
33
|
+
log_file_name: ".facy_log",
|
34
|
+
app_id: config['app_id'],
|
35
|
+
app_secret: config['app_secret'],
|
36
|
+
permission: config['permission'],
|
37
|
+
granted: config['granted'],
|
38
|
+
redirect_uri: "http://www.facebook.com/connect/login_success.html",
|
39
|
+
prompt: "\e[15;48;5;27m f \e[0m >> ",
|
40
|
+
stream_fetch_interval: 2,
|
41
|
+
notification_fetch_interval: 2,
|
42
|
+
output_interval: 3,
|
43
|
+
retry_interval: 2,
|
44
|
+
comments_view_num: 10,
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
def start(options={})
|
49
|
+
_init
|
50
|
+
|
51
|
+
EM.run do
|
52
|
+
Thread.start do
|
53
|
+
while buf = Readline.readline(config[:prompt], true)
|
54
|
+
execute(buf.strip)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
Thread.start do
|
59
|
+
EM.add_periodic_timer(config[:stream_fetch_interval]) do
|
60
|
+
facebook_stream_fetch
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Thread.start do
|
65
|
+
EM.add_periodic_timer(config[:output_interval]) do
|
66
|
+
periodic_output
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
Thread.start do
|
71
|
+
EM.add_periodic_timer(config[:notification_fetch_interval]) do
|
72
|
+
facebook_notification_fetch
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
Signal.trap("INT") { stop_process }
|
77
|
+
Signal.trap("TERM") { stop_process }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def mutex
|
82
|
+
@mutex ||= Mutex.new
|
83
|
+
end
|
84
|
+
|
85
|
+
def sync(&block)
|
86
|
+
mutex.synchronize do
|
87
|
+
block.call
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def async(&block)
|
92
|
+
Thread.start { block.call }
|
93
|
+
end
|
94
|
+
|
95
|
+
def stop_process
|
96
|
+
puts "\nfacy going to stop..."
|
97
|
+
Thread.new {
|
98
|
+
EventMachine.stop
|
99
|
+
}.join
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
extend Core
|
104
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Facy
|
2
|
+
module Facebook
|
3
|
+
attr_reader :authen_hash, :rest
|
4
|
+
|
5
|
+
module ConnectionStatus
|
6
|
+
NORMAL = 0
|
7
|
+
ERROR = 1
|
8
|
+
SUSPENDED = 2
|
9
|
+
end
|
10
|
+
|
11
|
+
def facebook_status
|
12
|
+
@status ||= ConnectionStatus::NORMAL
|
13
|
+
end
|
14
|
+
|
15
|
+
#RULE: all facebook method should be prefix with facebook
|
16
|
+
def facebook_stream_fetch
|
17
|
+
return unless facebook_status == ConnectionStatus::NORMAL
|
18
|
+
streams = @graph.get_connections("me", "home")
|
19
|
+
streams.each { |post| stream_print_queue << graph2item(post) }
|
20
|
+
log(:info, "fetch stream ok")
|
21
|
+
rescue Koala::Facebook::ServerError
|
22
|
+
retry_wait
|
23
|
+
rescue Koala::Facebook::APIError
|
24
|
+
expired_session
|
25
|
+
rescue Exception => e
|
26
|
+
error e
|
27
|
+
end
|
28
|
+
|
29
|
+
def facebook_notification_fetch
|
30
|
+
return unless facebook_status == ConnectionStatus::NORMAL
|
31
|
+
notifications = @graph.get_connections("me", "notifications")
|
32
|
+
notifications.each { |notifi| notification_print_queue << graph2item(notifi) }
|
33
|
+
log(:info, "fetch notification ok")
|
34
|
+
rescue Koala::Facebook::ServerError
|
35
|
+
retry_wait
|
36
|
+
rescue Koala::Facebook::APIError
|
37
|
+
expired_session
|
38
|
+
rescue Exception => e
|
39
|
+
error e
|
40
|
+
end
|
41
|
+
|
42
|
+
def facebook_post(text)
|
43
|
+
@graph.put_wall_post(text)
|
44
|
+
rescue Koala::Facebook::ServerError
|
45
|
+
retry_wait
|
46
|
+
rescue Koala::Facebook::APIError
|
47
|
+
expired_session
|
48
|
+
rescue Exception => e
|
49
|
+
error e
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def facebook_like(post_id)
|
54
|
+
@graph.put_like(post_id)
|
55
|
+
rescue Koala::Facebook::ServerError
|
56
|
+
retry_wait
|
57
|
+
rescue Koala::Facebook::APIError
|
58
|
+
expired_session
|
59
|
+
rescue Exception => e
|
60
|
+
error e
|
61
|
+
end
|
62
|
+
|
63
|
+
def facebook_set_seen(notification_id)
|
64
|
+
@graph.put_connection("#{notification_id}", "unread=false")
|
65
|
+
rescue Koala::Facebook::ServerError
|
66
|
+
retry_wait
|
67
|
+
rescue Koala::Facebook::APIError
|
68
|
+
expired_session
|
69
|
+
rescue Exception => e
|
70
|
+
error e
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
def facebook_comment(post_id, comment)
|
75
|
+
@graph.put_comment(post_id, comment)
|
76
|
+
rescue Koala::Facebook::ServerError
|
77
|
+
retry_wait
|
78
|
+
rescue Koala::Facebook::APIError
|
79
|
+
expired_session
|
80
|
+
rescue Exception => e
|
81
|
+
error e
|
82
|
+
end
|
83
|
+
|
84
|
+
def expired_session
|
85
|
+
FileUtils.rm(session_file)
|
86
|
+
instant_output(Item.new(info: :info, content: "Please restart facy to obtain new access token!"))
|
87
|
+
exit
|
88
|
+
end
|
89
|
+
|
90
|
+
def retry_wait
|
91
|
+
log(:error, "facebook server error, need retry")
|
92
|
+
instant_output(Item.new(info: :error, content: "facebook server error, retry in #{config[:retry_interval]} seconds"))
|
93
|
+
sleep(config[:retry_interval])
|
94
|
+
end
|
95
|
+
|
96
|
+
def login
|
97
|
+
token = config[:access_token]
|
98
|
+
@graph = Koala::Facebook::API.new(token)
|
99
|
+
log(:info, "login ok at facebook module: #{@graph}")
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
extend Facebook
|
104
|
+
|
105
|
+
init do
|
106
|
+
login
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Facy
|
4
|
+
module GetToken
|
5
|
+
def session_file
|
6
|
+
File.expand_path(config[:session_file_name], config[:session_file_folder])
|
7
|
+
end
|
8
|
+
|
9
|
+
def setup_app_id
|
10
|
+
developer_page = "https://developers.facebook.com"
|
11
|
+
puts "★ go to #{developer_page} and enter our app_id: "
|
12
|
+
browse(developer_page)
|
13
|
+
config[:app_id] = STDIN.gets.chomp
|
14
|
+
log(:info, "app_id setup success #{config[:app_id]}")
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup_app_secret
|
18
|
+
developer_page = "https://developers.facebook.com"
|
19
|
+
puts "★ go to #{developer_page} and enter our app_secret: "
|
20
|
+
config[:app_secret] = STDIN.gets.chomp
|
21
|
+
log(:info, "app_secret setup success #{config[:app_id]}")
|
22
|
+
end
|
23
|
+
|
24
|
+
def save_config_file
|
25
|
+
File.open(File.expand_path('../../../config.yml', __FILE__), 'w') do |f|
|
26
|
+
conf = {
|
27
|
+
"app_id" => config[:app_id],
|
28
|
+
"app_secret" => config[:app_secret],
|
29
|
+
"permission" => config[:permission],
|
30
|
+
"granted" => true
|
31
|
+
}
|
32
|
+
f.write conf.to_yaml
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_session_file
|
37
|
+
session = YAML.load_file(session_file)
|
38
|
+
config[:access_token] = session["access_token"]
|
39
|
+
return (ret = config[:access_token].nil? ? false : true)
|
40
|
+
log(:info, "session file load success #{config[:access_token]}")
|
41
|
+
rescue Errno::ENOENT #file not found
|
42
|
+
return false
|
43
|
+
end
|
44
|
+
|
45
|
+
def save_session_file
|
46
|
+
hash = {"access_token" => config[:access_token]}
|
47
|
+
File.open(session_file, "w") { |f| f.write hash.to_yaml }
|
48
|
+
log(:info, "session file save success at #{session_file}")
|
49
|
+
end
|
50
|
+
|
51
|
+
def exchange_long_term_token
|
52
|
+
oauth = Koala::Facebook::OAuth.new(config[:app_id], config[:app_secret])
|
53
|
+
new_token = oauth.exchange_access_token_info(config[:access_token])
|
54
|
+
if new_token["access_token"]
|
55
|
+
config[:access_token] = new_token["access_token"]
|
56
|
+
log(:info, "long term access token exchanged success")
|
57
|
+
else
|
58
|
+
log(:error, "long term access token exchanged failed")
|
59
|
+
raise Exception.new("can not accquire new access token") unless new_token["access_token"]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def grant_access
|
64
|
+
app_id = config[:app_id]
|
65
|
+
redirect_uri = config[:redirect_uri]
|
66
|
+
permission = config[:permission]
|
67
|
+
|
68
|
+
get_access_url =
|
69
|
+
"https://www.facebook.com/dialog/oauth?client_id=#{app_id}&scope=#{permission}&redirect_uri=#{redirect_uri}"
|
70
|
+
puts "★ goto #{get_access_url} to grant access to our app"
|
71
|
+
browse(get_access_url)
|
72
|
+
puts "→ after access granted press enter"
|
73
|
+
STDIN.gets
|
74
|
+
end
|
75
|
+
|
76
|
+
def setup_token
|
77
|
+
developer_page = "https://developers.facebook.com/tools/accesstoken/"
|
78
|
+
puts "★ goto #{developer_page} and enter User access token: "
|
79
|
+
browse(developer_page)
|
80
|
+
token = STDIN.gets.chomp
|
81
|
+
config[:access_token] = token
|
82
|
+
log(:info, "setup access token success: #{token}")
|
83
|
+
end
|
84
|
+
|
85
|
+
def login_flow
|
86
|
+
unless config[:app_id]
|
87
|
+
setup_app_id
|
88
|
+
end
|
89
|
+
unless config[:app_secret]
|
90
|
+
setup_app_secret
|
91
|
+
end
|
92
|
+
unless config[:granted]
|
93
|
+
grant_access
|
94
|
+
end
|
95
|
+
save_config_file
|
96
|
+
|
97
|
+
unless load_session_file
|
98
|
+
setup_token
|
99
|
+
exchange_long_term_token
|
100
|
+
save_session_file
|
101
|
+
end
|
102
|
+
log(:info, "login flow success")
|
103
|
+
end
|
104
|
+
|
105
|
+
def browse(url)
|
106
|
+
Launchy.open(url)
|
107
|
+
rescue
|
108
|
+
puts "warning: can't open url"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
extend GetToken
|
112
|
+
end
|
data/lib/facy/help.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Facy
|
2
|
+
module Help
|
3
|
+
def helps
|
4
|
+
@helps ||= []
|
5
|
+
end
|
6
|
+
|
7
|
+
def help(target, usage, example=nil)
|
8
|
+
helps << {target: target, usage: usage, example: example}
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
extend Help
|
13
|
+
init do
|
14
|
+
command :help do |target|
|
15
|
+
target = target.tap{|t|t.strip!}.gsub(':','').to_sym
|
16
|
+
@helps.each do |h|
|
17
|
+
if h[:target] == target
|
18
|
+
instant_output(Item.new(
|
19
|
+
info: :help,
|
20
|
+
content: h[:usage],
|
21
|
+
extra: h[:example]
|
22
|
+
))
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
help :help, "display usage for a command", ":help [command]"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module Facy
|
2
|
+
module ImageViewer
|
3
|
+
def view_img(path, options={})
|
4
|
+
Image.new(path, options).draw
|
5
|
+
end
|
6
|
+
|
7
|
+
module AnsiRGB
|
8
|
+
def self.wrap_with_code(string, rgb)
|
9
|
+
red, green, blue = rgb[0], rgb[1], rgb[2]
|
10
|
+
"\e[#{code(red, green, blue)}m" + string + "\e[0m"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.code(red, green, blue)
|
14
|
+
index = 16 +
|
15
|
+
to_ansi_domain(red) * 36 +
|
16
|
+
to_ansi_domain(green) * 6 +
|
17
|
+
to_ansi_domain(blue)
|
18
|
+
"48;5;#{index}"
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.to_ansi_domain(value)
|
22
|
+
(6 * (value / 256.0)).to_i
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class Image
|
27
|
+
def initialize(path, options={})
|
28
|
+
@image = Magick::ImageList.new(path)
|
29
|
+
@fit_terminal = !!options[:fit_terminal]
|
30
|
+
rescue Exception => e
|
31
|
+
p e.message
|
32
|
+
end
|
33
|
+
|
34
|
+
def draw
|
35
|
+
convert_to_fit_terminal_size if @fit_terminal
|
36
|
+
rgb_analyze
|
37
|
+
ansi_analyze
|
38
|
+
puts_ansi
|
39
|
+
end
|
40
|
+
|
41
|
+
def rgb_analyze
|
42
|
+
@rgb = []
|
43
|
+
cols = @image.columns
|
44
|
+
rows = @image.rows
|
45
|
+
rows.times do |y|
|
46
|
+
cols.times do |x|
|
47
|
+
@rgb[y] ||= []
|
48
|
+
pixcel = @image.pixel_color(x, y)
|
49
|
+
r = pixcel.red / 256
|
50
|
+
g = pixcel.green / 256
|
51
|
+
b = pixcel.blue / 256
|
52
|
+
@rgb[y] << [r, g, b]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def ansi_analyze
|
58
|
+
raise "use rgb_analyze before ansi_analyze" unless @rgb
|
59
|
+
ret = []
|
60
|
+
@rgb.map! do |row|
|
61
|
+
ret << row.map{|pixcel|
|
62
|
+
AnsiRGB.wrap_with_code(@double ? " " : " ", pixcel)
|
63
|
+
}.join
|
64
|
+
end
|
65
|
+
@ansi = ret.join("\n")
|
66
|
+
rescue Exception => e
|
67
|
+
error e.message
|
68
|
+
end
|
69
|
+
|
70
|
+
def puts_ansi
|
71
|
+
raise "use ansi_analyze before to_ansi" unless @ansi
|
72
|
+
puts @ansi
|
73
|
+
end
|
74
|
+
|
75
|
+
def get_term_size
|
76
|
+
`stty size`.split(" ").map(&:to_i)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
extend ImageViewer
|
82
|
+
end
|