fuck_facebook 0.1.1 → 0.4.0

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 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