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.
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,9 @@
1
+ class String
2
+ def colorize(*code)
3
+ "\e[#{code.join(";")}m#{self}\e[0m"
4
+ end
5
+
6
+ def strip
7
+ self.truncate(50)
8
+ end
9
+ end
@@ -0,0 +1,3 @@
1
+ module Facy
2
+ class FacebookGraphReqError < Exception ; end
3
+ 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