facy 1.2.9.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|