fuck_facebook 0.1.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '059a4dd680f130343508367f8a4d661ea3de1ef4bd74fa6b700d9ddd323e41c4'
4
- data.tar.gz: 68d9b31fcc64272a7c7df41764ffda58c4066af19fc7132346e7dcfc7ab0ceaa
3
+ metadata.gz: 24caa7bf76958d703667ca687fb2bc9cf4aa7b6ea2bc56db68e08a13ab74bc08
4
+ data.tar.gz: 540bdba30c7e8b0761fde58f30d87decfbd6593f137e2d293e5a8b292c1c180d
5
5
  SHA512:
6
- metadata.gz: c09a4393cab84897c62bd0c980dd8e2a7315f94e34fa181bd2d0f43346c5ecfd9e9e04fd579e86095633cb8d79a93f908a004a67673dccda7c1751ff0f40e36c
7
- data.tar.gz: d3f9ad82007908d96c2d3a96d85fb7436af46643bc4e7ca08341f7e93f458bbd9e41248c92caa06fbf817fd08b43d0e3fc8978bec62c0ada6e267d6dfee4b555
6
+ metadata.gz: fd41131b5187fe85b56adcb45d3d1f857d109bb925c0cf65012baf4aaf195d67317484593764a869eaf2b2ed18f336b98f47346031cfe000c1ced8cb1823afc7
7
+ data.tar.gz: 0c1873d36d15249e85311aabf4de4ed4517b0f378881a032e871b96a35d0c02db6113fe65f70980896633680df85efe70155d230d18e4c2824cf9d67e88dae7d
data/.ruby-version CHANGED
@@ -1 +1 @@
1
- 3.0.1
1
+ 3.0.2
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,23 @@
1
+ # Developer Guide
2
+
3
+ ## Pushing a new version of the gem
4
+
5
+ **Note:** You must have been granted access to deploy new versions of the `fuck_facebook` gem
6
+
7
+ * Bump the version in `lib/fuck_facebook/version.rb`
8
+ * Build the gem with `gem build fuck_facebook.gemspec`
9
+ * Push the gem with `gem push fuck_facebook-VERSION.gem` replacing `VERSION` with the new version number
10
+
11
+ ## Pushing a new version of the Docker image
12
+
13
+ **Note:** You must have been granted access to deploy new versions of the `fuck_facebook` Docker image
14
+
15
+ * Build the image:
16
+
17
+ ```
18
+ docker build . -t keeyan/fuck_facebook:VERSION --build-arg version=VERSION
19
+ ```
20
+
21
+ Replace `VERSION` with the version number
22
+
23
+ * Push the image with `docker push keeyan/fuck_facebook:VERSION` replacing `VERSION` with the version number
data/Dockerfile ADDED
@@ -0,0 +1,15 @@
1
+ FROM ruby:3.0.2-bullseye
2
+
3
+ RUN apt-get update && apt-get install -y chromium-driver
4
+
5
+ RUN useradd --create-home --shell /bin/bash fuckfacebook
6
+
7
+ USER fuckfacebook
8
+ WORKDIR /home/fuckfacebook
9
+
10
+ RUN mkdir -p /home/fuckfacebook/.local/share/fuck-facebook
11
+ VOLUME /home/fuckfacebook/.local/share/fuck-facebook
12
+
13
+ ARG version
14
+ COPY fuck_facebook-$version.gem .
15
+ RUN gem install fuck_facebook-$version.gem
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- fuck_facebook (0.1.1)
4
+ fuck_facebook (0.2.1)
5
5
  activesupport (~> 6.1)
6
6
  highline (~> 2.0)
7
7
  mail (~> 2.7)
@@ -10,7 +10,7 @@ PATH
10
10
  GEM
11
11
  remote: https://rubygems.org/
12
12
  specs:
13
- activesupport (6.1.4)
13
+ activesupport (6.1.4.4)
14
14
  concurrent-ruby (~> 1.0, >= 1.0.2)
15
15
  i18n (>= 1.6, < 2)
16
16
  minitest (>= 5.1)
@@ -21,13 +21,13 @@ GEM
21
21
  coderay (1.1.3)
22
22
  concurrent-ruby (1.1.9)
23
23
  highline (2.0.3)
24
- i18n (1.8.10)
24
+ i18n (1.8.11)
25
25
  concurrent-ruby (~> 1.0)
26
26
  mail (2.7.1)
27
27
  mini_mime (>= 0.1.1)
28
28
  method_source (1.0.0)
29
- mini_mime (1.1.0)
30
- minitest (5.14.4)
29
+ mini_mime (1.1.2)
30
+ minitest (5.15.0)
31
31
  parallel (1.20.1)
32
32
  parser (3.0.1.1)
33
33
  ast (~> 2.4.1)
@@ -56,7 +56,7 @@ GEM
56
56
  tzinfo (2.0.4)
57
57
  concurrent-ruby (~> 1.0)
58
58
  unicode-display_width (2.0.0)
59
- zeitwerk (2.4.2)
59
+ zeitwerk (2.5.1)
60
60
 
61
61
  PLATFORMS
62
62
  ruby
@@ -67,4 +67,4 @@ DEPENDENCIES
67
67
  rubocop
68
68
 
69
69
  BUNDLED WITH
70
- 2.2.15
70
+ 2.2.22
data/README.md CHANGED
@@ -1,3 +1,48 @@
1
1
  # Fuck Facebook
2
2
 
3
3
  A collection of tools to help you use Facebook when you don't want to
4
+
5
+ ## Usage
6
+
7
+ Run `fuck-facebook --help` to get a list of options and explanations.
8
+
9
+ To get all unread messages (that haven't already been reported) run these commands:
10
+
11
+ ```
12
+ fuck-facebook --login
13
+ ```
14
+
15
+ You only need to login once
16
+
17
+ ```
18
+ fuck-facebook --unread-messages
19
+ ```
20
+
21
+ This will only show you unread messages that have been received since the last time you checked.
22
+ This way if you have emails going out for new messages, you won't get notified for the same message each time.
23
+
24
+ ## Config
25
+
26
+ Here's a complete config file along with an explanation of each option:
27
+
28
+ ```yaml
29
+ smtp:
30
+ from_email: sender@example.com
31
+ to_email: receiver@example.com
32
+ address: smtp.example.com
33
+ port: 587
34
+ user_name: some_user_name
35
+ password: 123456
36
+ authentication: plain
37
+ ```
38
+
39
+ Place this file in `~/.local/share/fuck-facebook/config.yaml`
40
+
41
+ These options can be passed as environment variables, by:
42
+ * Joining the keys with `_`
43
+ * Capitalizing the entire string
44
+ * Prefixing the key with `FF_`
45
+
46
+ For example the `smtp` `from_email` will become `FF_SMTP_FROM_EMAIL`
47
+
48
+ Environment variables take precedent over the config file.
data/bin/fuck-facebook CHANGED
@@ -2,65 +2,27 @@
2
2
 
3
3
  require 'optparse'
4
4
  require 'mail'
5
- require 'pry'
6
5
 
7
6
  require_relative '../src/facebook_connection'
8
7
  require_relative '../src/config'
9
8
  require_relative '../src/initializers/mail_initializer'
10
9
  require_relative '../src/senders'
11
-
12
- options = {
13
- headless: true,
14
- clear_cookies: false,
15
- unread_messages: false,
16
- messages: false,
17
- sender: 'stdout'
18
- }
19
-
20
- OptionParser.new do |opts|
21
- opts.banner = 'Usage: fuck-facebook.rb [options]'
22
-
23
- opts.on('-h', '--[no-]headless', 'Run in a headless browser') do |v|
24
- options[:headless] = v
25
- end
26
-
27
- opts.on('--clear-cookies') do |v|
28
- options[:clear_cookies] = v
29
- end
30
-
31
- opts.on('--unread-messages') do |v|
32
- options[:unread_messages] = v
33
- end
34
-
35
- opts.on('--messages') do |v|
36
- options[:messages] = v
37
- end
38
-
39
- opts.on('--login') do |v|
40
- options[:login] = v
41
- end
42
-
43
- opts.on('--sender=SENDER') do |sender|
44
- options[:sender] = sender
45
- end
46
- end.parse!
47
-
48
- facebook_connection = FacebookConnection.new(headless: options[:headless])
49
-
50
- facebook_connection.cookie_handler.clear! if options[:clear_cookies]
51
-
52
- facebook_connection.login if options[:login]
53
-
54
- messages =
55
- if options[:unread_messages]
56
- facebook_connection.message_handler.unread
57
- elsif options[:messages]
58
- facebook_connection.message_handler.messages
59
- else
60
- []
61
- end
62
-
63
- sender = Senders.by_name(options[:sender])
64
- sender.send_messages(messages)
65
-
66
- facebook_connection.close
10
+ require_relative '../src/args_parser'
11
+ require_relative '../src/message_sender'
12
+ require_relative '../src/error_reporter'
13
+
14
+ options = ArgsParser.parse_args
15
+
16
+ begin
17
+ raise 'Error triggered by --simulate-error flag' if options[:simulate_error]
18
+
19
+ facebook_connection = FacebookConnection.new(headless: options[:headless])
20
+ facebook_connection.cookie_handler.clear! if options[:clear_cookies]
21
+ facebook_connection.login if options[:login]
22
+ message_sender = MessageSender.new(options[:sender], facebook_connection)
23
+ message_sender.send_unread_messages if options[:unread_messages]
24
+ message_sender.send_messages if options[:messages]
25
+ facebook_connection.close
26
+ rescue StandardError => e
27
+ ErrorReporter.report_error(options[:sender], e)
28
+ end
@@ -1,3 +1,3 @@
1
1
  module FuckFacebook
2
- VERSION = '0.1.1'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
@@ -0,0 +1,82 @@
1
+ class ArgsParser
2
+ DEFAULT_OPTIONS = {
3
+ headless: true,
4
+ clear_cookies: false,
5
+ unread_messages: false,
6
+ messages: false,
7
+ sender: 'stdout',
8
+ simulate_error: false
9
+ }.freeze
10
+
11
+ AVAILABLE_OPTIONS = [
12
+ {
13
+ short: '-h',
14
+ long: '--[no-]headless',
15
+ message: 'Run in a headless browser',
16
+ option: :headless
17
+ },
18
+ {
19
+ short: nil,
20
+ long: '--clear-cookies',
21
+ message: 'Clears all cookies',
22
+ option: :clear_cookies
23
+ },
24
+ {
25
+ short: nil,
26
+ long: '--unread-messages',
27
+ message: 'Outputs all unread messages sent since the last check',
28
+ option: :unread_messages
29
+ },
30
+ {
31
+ short: nil,
32
+ long: '--messages',
33
+ message: 'Outputs all messages sent since the last check',
34
+ option: :messages
35
+ },
36
+ {
37
+ short: nil,
38
+ long: '--login',
39
+ message: 'Prompts for details to login to Facebook account',
40
+ option: :login
41
+ },
42
+ {
43
+ short: nil,
44
+ long: '--sender=SENDER',
45
+ message: 'Send results to either stdout or email',
46
+ option: :sender
47
+ },
48
+ {
49
+ short: nil,
50
+ long: '--simulate-error',
51
+ message: 'Simulates an error so you can see how it will be outputted',
52
+ option: :simulate_error
53
+ }
54
+ ].freeze
55
+
56
+ def self.parse_args
57
+ passed_options = DEFAULT_OPTIONS.dup
58
+
59
+ OptionParser.new do |opts|
60
+ generate_banner(opts)
61
+
62
+ AVAILABLE_OPTIONS.each do |available_option|
63
+ parser_for_available_option(opts, available_option, passed_options)
64
+ end
65
+ end.parse!
66
+
67
+ passed_options
68
+ end
69
+
70
+ private_class_method def self.generate_banner(opts)
71
+ opts.banner = 'Usage: fuck-facebook.rb [options]'
72
+ end
73
+
74
+ private_class_method def self.parser_for_available_option(opts, available_option, passed_options)
75
+ args = [available_option[:long], available_option[:message]]
76
+ args = [available_option[:short]] + args if available_option[:short]
77
+
78
+ opts.on(*args) do |v|
79
+ passed_options[available_option[:option]] = v
80
+ end
81
+ end
82
+ end
data/src/authenticator.rb CHANGED
@@ -19,7 +19,7 @@ class Authenticator
19
19
 
20
20
  accept_cookies
21
21
  submit_credentials
22
- submit_2fa
22
+ submit_otp
23
23
  click_remember_password
24
24
  end
25
25
 
@@ -32,7 +32,7 @@ class Authenticator
32
32
 
33
33
  def click_remember_password
34
34
  driver.navigate.to('https://www.facebook.com/settings?tab=security')
35
- driver.switch_to.frame(xpaths.element(:settings, :security_iframe))
35
+ driver.switch_to.frame(xpaths.wait_for_element(:settings, :security_iframe))
36
36
  xpaths.element(:settings, :save_login_information_edit_button).click
37
37
  xpaths.element(:settings, :save_login_information_button).click
38
38
  end
@@ -43,15 +43,20 @@ class Authenticator
43
43
  xpaths.element(:login_button).click
44
44
  end
45
45
 
46
- def submit_2fa
47
- return unless xpaths.element(:checkpoint, :prompt).text == 'Two-factor authentication required'
46
+ def submit_otp
47
+ return unless xpaths.element_exists?(:checkpoint, :otp_prompt)
48
48
 
49
49
  xpaths.element(:checkpoint, :otp_input).send_keys(user_input.get(:otp))
50
- xpaths.element(:checkpoint, :otp_continue_button).click
51
- xpaths.element(:checkpoint, :save_browser_continue_button).click
50
+ otp_button = xpaths.element(:checkpoint, :submit_button)
51
+ otp_button.click
52
+
53
+ save_browser_button = xpaths.element(:checkpoint, :submit_button)
54
+ save_browser_button.click
52
55
  end
53
56
 
54
57
  def accept_cookies
58
+ return unless xpaths.element_exists?(:accept_cookies_button)
59
+
55
60
  accept_cookies_button = xpaths.element(:accept_cookies_button)
56
61
  accept_cookies_button.click
57
62
  end
data/src/config.rb CHANGED
@@ -1,9 +1,27 @@
1
1
  require 'yaml'
2
2
 
3
3
  class Config
4
- CONFIG = YAML.load_file("#{ENV['HOME']}/.config/fuck-facebook/config.yaml", symbolize_names: true)
4
+ CONFIG_FILE_PATH = "#{ENV['HOME']}/.local/share/fuck-facebook/config.yaml".freeze
5
5
 
6
6
  def self.option(*path)
7
- CONFIG.dig(*path)
7
+ env_var = "FF_#{path.join('_').upcase}"
8
+ return ENV[env_var] if ENV[env_var]
9
+
10
+ path_strings = path.map(&:to_s)
11
+ config.dig(*path_strings)
12
+ end
13
+
14
+ private
15
+
16
+ def self.config
17
+ create_config_file_if_not_exists!
18
+
19
+ YAML.load_file(CONFIG_FILE_PATH)
20
+ end
21
+
22
+ def self.create_config_file_if_not_exists!
23
+ dirname = File.dirname(CONFIG_FILE_PATH)
24
+ FileUtils.mkdir_p(dirname) unless File.directory?(dirname)
25
+ File.write(CONFIG_FILE_PATH, '{}') unless File.exist?(CONFIG_FILE_PATH)
8
26
  end
9
27
  end
@@ -41,7 +41,7 @@ class CookieHandler
41
41
  saved_cookies = JSON.parse(File.read(cookies_filename), symbolize_names: true)
42
42
 
43
43
  saved_cookies.each do |saved_cookie|
44
- saved_cookie[:expires] = Time.parse(saved_cookie[:expires])
44
+ saved_cookie[:expires] = Time.parse(saved_cookie[:expires]) unless saved_cookie[:expires].nil?
45
45
  end
46
46
 
47
47
  saved_cookies
@@ -0,0 +1,6 @@
1
+ class ErrorReporter
2
+ def self.report_error(sender_name, error)
3
+ Senders.by_name(sender_name).send_error(error)
4
+ raise error
5
+ end
6
+ end
@@ -31,6 +31,8 @@ class FacebookConnection
31
31
 
32
32
  options = Selenium::WebDriver::Chrome::Options.new
33
33
  options.add_argument('--disable-notifications')
34
+ options.add_argument('--no-sandbox')
35
+ options.add_argument('--disable-dev-shm-usage')
34
36
  options.add_argument('--headless') if headless
35
37
  options.add_option('detach', true) unless headless
36
38
 
@@ -18,10 +18,7 @@ class MessageHandler
18
18
  def messages
19
19
  visit_messages_page
20
20
 
21
- xpaths.wait_for_element(:messages, :chats)
22
-
23
- chats = xpaths.elements(:messages, :chats)
24
-
21
+ chats = xpaths.wait_for_elements(:messages, :chats)
25
22
  messages = messages_for_chats(chats)
26
23
 
27
24
  messages_after_last_message_time(messages)
@@ -0,0 +1,25 @@
1
+ class MessageSender
2
+ attr_accessor :sender_name, :facebook_connection
3
+
4
+ def initialize(sender_name, facebook_connection)
5
+ @sender_name = sender_name
6
+ @facebook_connection = facebook_connection
7
+ end
8
+
9
+ def send_unread_messages
10
+ messages = facebook_connection.message_handler.unread
11
+ send_array_of_messages(messages)
12
+ end
13
+
14
+ def send_messages
15
+ messages = facebook_connection.message_handler.messages
16
+ send_array_of_messages(messages)
17
+ end
18
+
19
+ private
20
+
21
+ def send_array_of_messages(messages)
22
+ sender = Senders.by_name(sender_name)
23
+ sender.send_messages(messages)
24
+ end
25
+ end
data/src/senders/email.rb CHANGED
@@ -10,5 +10,14 @@ module Senders
10
10
  end
11
11
  end
12
12
  end
13
+
14
+ def self.send_error(error)
15
+ Mail.deliver do
16
+ from Config.option(:smtp, :from_email)
17
+ to Config.option(:smtp, :to_email)
18
+ subject 'Error occurred during Fuck Facebook command'
19
+ body error.full_message(highlight: false)
20
+ end
21
+ end
13
22
  end
14
23
  end
@@ -5,5 +5,9 @@ module Senders
5
5
  puts message
6
6
  end
7
7
  end
8
+
9
+ def self.send_error(error)
10
+ puts error
11
+ end
8
12
  end
9
13
  end
data/src/storage.rb CHANGED
@@ -29,7 +29,7 @@ class Storage
29
29
  def self.storage
30
30
  create_storage_file_if_not_exists!
31
31
 
32
- YAML.load_file(STORAGE_FILE_PATH, symbolize_names: true)
32
+ YAML.load_file(STORAGE_FILE_PATH)
33
33
  end
34
34
 
35
35
  def self.create_storage_file_if_not_exists!
data/src/xpaths.rb CHANGED
@@ -63,17 +63,24 @@ class Xpaths
63
63
  found_element
64
64
  end
65
65
 
66
+ def wait_for_elements(*path_elements)
67
+ wait_for_element(*path_elements)
68
+
69
+ elements(*path_elements)
70
+ end
71
+
66
72
  def for(*path_elements)
67
- result = XPATHS.dig(*path_elements)
73
+ path_elements_strings = path_elements.map(&:to_s)
74
+ result = XPATHS.dig(*path_elements_strings)
68
75
 
69
- raise 'Could not find xpath' if result.empty?
76
+ raise 'Could not find xpath' if result.nil? || result.empty?
70
77
 
71
78
  result
72
79
  end
73
80
 
74
81
  private_class_method def self.xpaths_from_file
75
82
  xpaths_file = File.join(File.dirname(__FILE__), '../xpaths.yaml')
76
- YAML.load_file(xpaths_file, symbolize_names: true)
83
+ YAML.load_file(xpaths_file)
77
84
  end
78
85
 
79
86
  XPATHS = xpaths_from_file
data/xpaths.yaml CHANGED
@@ -8,13 +8,12 @@ settings:
8
8
  save_login_information_button: '//div[text() = "Save your login information"]'
9
9
  link_to_profile: '//a[@href="/me/"]'
10
10
  checkpoint:
11
- otp_input: '/html/body/div[1]/div[3]/div[1]/div/form/div/div[2]/div[3]/span/input'
12
- otp_continue_button: '/html/body/div[1]/div[3]/div[1]/div/form/div/div[3]/div[1]/button'
11
+ otp_prompt: '//strong[text() = "Two-factor authentication required"]'
12
+ otp_input: '//*[@id="approvals_code"]'
13
13
  save_browser_radio_input: '/html/body/div[1]/div[3]/div[1]/div/form/div/div[2]/div[2]/div[1]/input'
14
- save_browser_continue_button: '/html/body/div[1]/div[3]/div[1]/div/form/div/div[3]/div[1]/button'
15
- prompt: '/html/body/div[1]/div[3]/div[1]/div/form/div/div[1]'
14
+ submit_button: '//*[@id="checkpointSubmitButton"]'
16
15
  messages:
17
- chats: '/html/body/div[1]/div/div[1]/div/div[3]/div/div/div[1]/div[1]/div[1]/div/div/div/div[3]/div[1]/div[2]/div/child::*[@role = "row"]'
16
+ chats: './/*[@aria-label="Chats"]/child::*'
18
17
  title_relative: 'div[1]/div/a/div/div[2]/div[1]/div/div/div[1]/span/span/span/span'
19
18
  text_relative: 'div[1]/div/a/div/div[2]/div[1]/div/div/div[2]/span/span/div/span[1]/span'
20
19
  time_sent_ago_relative: 'div[1]/div/a/div/div[2]/div[1]/div/div/div[2]/span/span/div/span[3]'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fuck_facebook
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keeyan Nejad
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-15 00:00:00.000000000 Z
11
+ date: 2021-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -81,6 +81,8 @@ files:
81
81
  - ".rubocop.yml"
82
82
  - ".ruby-version"
83
83
  - CHANGELOG.md
84
+ - CONTRIBUTING.md
85
+ - Dockerfile
84
86
  - Gemfile
85
87
  - Gemfile.lock
86
88
  - LICENSE.txt
@@ -92,13 +94,16 @@ files:
92
94
  - fuck_facebook.gemspec
93
95
  - lib/fuck_facebook.rb
94
96
  - lib/fuck_facebook/version.rb
97
+ - src/args_parser.rb
95
98
  - src/authenticator.rb
96
99
  - src/config.rb
97
100
  - src/cookie_handler.rb
101
+ - src/error_reporter.rb
98
102
  - src/facebook_connection.rb
99
103
  - src/fb_duration.rb
100
104
  - src/initializers/mail_initializer.rb
101
105
  - src/message_handler.rb
106
+ - src/message_sender.rb
102
107
  - src/models/message.rb
103
108
  - src/models/message_thread.rb
104
109
  - src/models/user.rb
@@ -135,7 +140,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
140
  - !ruby/object:Gem::Version
136
141
  version: '0'
137
142
  requirements: []
138
- rubygems_version: 3.2.15
143
+ rubygems_version: 3.2.22
139
144
  signing_key:
140
145
  specification_version: 4
141
146
  summary: A collection of tools to help you use Facebook when you don't want to