FbRuby 0.0.2

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.
@@ -0,0 +1,412 @@
1
+ require 'cgi'
2
+ require 'zlib'
3
+ require 'json'
4
+ require 'stringio'
5
+ require 'nokogiri'
6
+ require 'rest-client'
7
+ require_relative 'exceptions.rb'
8
+
9
+ # https://stackoverflow.com/a/34678301/14861946
10
+ Nokogiri::XML::Node.send(:define_method, 'xpath_regex') { |*args|
11
+ xpath = args[0]
12
+ rgxp = /\/([a-z]+)\[@([a-z\-]+)~=\/(.*?)\/\]/
13
+ xpath.gsub!(rgxp) { |s| m = s.match(rgxp); "/#{m[1]}[regex(.,'#{m[2]}','#{m[3]}')]" }
14
+ self.xpath(xpath, Class.new {
15
+ def regex node_set, attr, regex
16
+ node_set.find_all { |node| node[attr] =~ /#{regex}/ }
17
+ end
18
+ }.new)
19
+ }
20
+
21
+ class RestClient::Response
22
+
23
+ #include RestClient::AbstractResponse
24
+
25
+ def body(decompress = true)
26
+ encoding = self.headers[:content_encoding]
27
+ response = String.new(self)
28
+
29
+ if encoding == "gzip" and decompress and !response.empty?
30
+ return Zlib::GzipReader.new(StringIO.new(response)).read
31
+ elsif encoding == "deflate" and decompress and !response.empty?
32
+ return Zlib::Inflate.inflate(response)
33
+ else
34
+ return response
35
+ end
36
+ end
37
+
38
+ def to_s
39
+ return body
40
+ end
41
+
42
+ def ok?
43
+ if (400..500).to_a.member? (code)
44
+ return false
45
+ else
46
+ return true
47
+ end
48
+ end
49
+
50
+ def parse_html
51
+ return Nokogiri::HTML(body)
52
+ end
53
+
54
+ def parse_xml
55
+ return Nokogiri::XML(body)
56
+ end
57
+
58
+ def json
59
+ return JSON.parse(body)
60
+ end
61
+ end
62
+
63
+ class RestClient::Payload::Multipart
64
+ def create_file_field(s, k, v)
65
+ begin
66
+ s.write("Content-Disposition: form-data;")
67
+ s.write(" name=\"#{k}\";") unless (k.nil? || k=='')
68
+ s.write(" filename=\"#{v.respond_to?(:original_filename) ? v.original_filename : File.basename(v.path)}\"#{EOL}")
69
+ s.write("Content-Type: #{v.respond_to?(:content_type) ? v.content_type : mime_for(v.path)}#{EOL}")
70
+ s.write(EOL)
71
+ while (data = v.read(8124))
72
+ s.write(data)
73
+ end
74
+ rescue IOError
75
+ ensure
76
+ v.close if v.respond_to?(:close)
77
+ end
78
+ end
79
+ end
80
+
81
+ module FbRuby
82
+ # utility
83
+ module Utils
84
+ # requests Session
85
+ class Session
86
+
87
+ @@default_user_agent = "Mozilla/5.0 (Linux; Android 9; SM-N976V) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.89 Mobile Safari/537.36"
88
+ @@headers = {'Accept-Encoding'=> 'gzip, deflate', 'Accept'=> '*/*', 'Connection'=> 'keep-alive'}
89
+ @@facebook_exceptions = true
90
+
91
+ class << self
92
+ def default_user_agent
93
+ return @@default_user_agent
94
+ end
95
+
96
+ def default_headers
97
+ return @@headers
98
+ end
99
+
100
+ def facebook_exceptions
101
+ return @@facebook_exceptions
102
+ end
103
+ end
104
+
105
+ attr_reader :options, :headers, :cookies
106
+
107
+ def initialize(headers = {}, cookies = {})
108
+ @headers = headers.empty? ? @@headers : headers
109
+
110
+ if cookies.instance_of?(Hash)
111
+ @cookies = cookies
112
+ else
113
+ @cookies = FbRuby::Utils::parse_cookies_header(cookies)
114
+ end
115
+
116
+ @options = {
117
+ verify_ssl: true,
118
+ max_redirects: 10,
119
+ timeout: 30,
120
+ open_timeout: 30,
121
+ user_agent: @headers.member?('user-agent') ? @headers['user-agent'] : @@default_user_agent,
122
+ follow_redirect: true}
123
+ end
124
+
125
+ def get(url, params = {}, headers = {}, options = {})
126
+ if params.length > 0
127
+ url = URI(url.to_s)
128
+ url.query = URI.encode_www_form(params.to_a)
129
+ end
130
+
131
+ request(:get, url, {}, headers, nil, options)
132
+ end
133
+
134
+ def post(url, data = {}, headers = {}, options = {})
135
+ request(:post, url, {}, headers, data, options)
136
+ end
137
+
138
+ def put(url, data = {}, headers = {}, options = {})
139
+ request(:put, url, {}, headers, data, options)
140
+ end
141
+
142
+ def delete(url, headers = {}, options = {})
143
+ request(:delete, url, {}, headers, nil, options)
144
+ end
145
+
146
+ def head(url, headers = {}, options = {})
147
+ request(:head, url, {}, headers, nil, options)
148
+ end
149
+
150
+ def get_without_sessions(url)
151
+ return RestClient.get(url.to_s, cookies: @cookies)
152
+ end
153
+
154
+ def post_without_sessions(url, data = {})
155
+ begin
156
+ return RestClient.post(url.to_s, data, cookies: @cookies)
157
+ rescue RestClient::Found => err
158
+ return get_without_sessions(err.response.headers[:location])
159
+ end
160
+ end
161
+
162
+ def get_cookie_str
163
+ return FbRuby::Utils::cookie_hash_to_str(@cookies)
164
+ end
165
+
166
+ def get_cookie_dict
167
+ if @cookies.instance_of? (Hash)
168
+ return @cookies
169
+ else
170
+ return FbRuby::Utils::parse_cookies_header(@cookies)
171
+ end
172
+ end
173
+
174
+ def get_cookie_hash
175
+ return get_cookie_dict
176
+ end
177
+
178
+ private
179
+
180
+ def request(method, url, params = {}, headers = {}, data = nil, options = {})
181
+ url = url.to_s if url.kind_of? (URI)
182
+ # url = URI::DEFAULT_PARSER.escape(url)
183
+ headers = @headers.merge(headers)
184
+ headers['Cookies'] = @cookies.map { |k, v| "#{k}=#{v}" }.join('; ') unless @cookies.empty?
185
+
186
+ begin
187
+ response = RestClient::Request.execute(method: method,url: url,headers: headers,cookies: @cookies,payload: data,params: params,**@options.merge(options)) do |response|
188
+ if [301,302, 307].include? (response.code)
189
+ # request(method,response.headers[:location],params,headers,data,options)
190
+ response.follow_redirection
191
+ else
192
+ response.return!
193
+ end
194
+ end
195
+ rescue RestClient::MovedPermanently, RestClient::Found => req_err
196
+ new_url = req_err.response.headers[:location]
197
+ response = get(new_url)
198
+ rescue RestClient::ExceptionWithResponse => req_err
199
+ response = req_err.response
200
+ end
201
+
202
+ # Perbarui cookie setelah menerima respons
203
+ update_cookies(response.cookies)
204
+
205
+ if @@facebook_exceptions
206
+ html = response.parse_html
207
+ unless html.at_xpath("//a[starts-with(@href,\"/home.php?rand=\")]").nil?
208
+ bugnub = html.xpath_regex("//a[@href~=/^\/bugnub\/\?(.*)=ErrorPage/]").first
209
+ div_err = html.at_css('div#root')
210
+ err_msg = "Terjadi Kesalahan :("
211
+ div_err_msg = div_err.at_css('div[class]')
212
+ err_msg = div_err_msg.css('text()').map(&:text).join(" ") unless div_err_msg.nil?
213
+
214
+ if bugnub.nil?
215
+ raise FbRuby::Exceptions::PageNotFound.new(err_msg)
216
+ else
217
+ raise FbRuby::Exceptions::FacebookError.new(err_msg)
218
+ end
219
+ end
220
+
221
+ if !html.at_css('div#root[@role="main"]').nil? && !html.at_css('a[class][@target="_self"][@href^="/?"]').nil? #html.at_css("a[@href^=\"#{URI.join(@url, '/help/contact/')}\"]").nil?
222
+ err_msg = html.at_css('div#root[@role="main"]')
223
+ err_msg = err_msg.parent unless err_msg.parent.nil?
224
+
225
+ raise FbRuby::Exceptions::AccountTemporaryBanned.new(err_msg.css('text()').map(&:text).join("\n"))
226
+ end
227
+
228
+
229
+ end
230
+
231
+ return response
232
+ end
233
+
234
+ def update_cookies(cookie_header)
235
+ return if cookie_header.nil? || cookie_header.empty?
236
+
237
+ cookie_header.each do |key, value|
238
+ @cookies[key] = value
239
+ end
240
+ end
241
+ end
242
+
243
+ class ThreadPool
244
+
245
+ def initialize(size:)
246
+ @size = size
247
+ @jobs = Queue.new
248
+ @pool = Array.new(size) do
249
+ Thread.new do
250
+ catch(:exit) do
251
+ loop do
252
+ job, args = @jobs.pop
253
+ job.call(*args)
254
+ end
255
+ end
256
+ end
257
+ end
258
+ end
259
+
260
+ def schedule(*args, &block)
261
+ @jobs << [block, args]
262
+ end
263
+
264
+ def shutdown
265
+ @size.times do
266
+ schedule { throw :exit }
267
+ end
268
+
269
+ @pool.map(&:join)
270
+ end
271
+ end
272
+
273
+ def self.randomString(length)
274
+ chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
275
+ return Array.new(length) { chars[rand(chars.size)] }.join
276
+ end
277
+
278
+ def self.parse_cookies_header(value)
279
+ return {} unless value
280
+
281
+ value.split(/; */n).each_with_object({}) do |cookie, cookies|
282
+ next if cookie.empty?
283
+ key, value = cookie.split('=', 2)
284
+ cookies[key] = (unescape(value) rescue value) unless cookies.key?(key)
285
+ end
286
+ end
287
+
288
+ def self.cookie_hash_to_str(cookie_hash, chomp = true, strip = true)
289
+ if cookie_hash.instance_of? (Hash)
290
+ cookies = cookie_hash.map{|key, value| "#{key}=#{value}"}.compact.join(';')
291
+ cookies.strip! if strip
292
+ cookies.chomp! if chomp
293
+
294
+ return cookies
295
+ end
296
+ end
297
+
298
+ def self.convert_file_size(size_in_bytes)
299
+ units = %w[bytes KB MB GB TB PB EB]
300
+ return "#{size_in_bytes} #{units[0]}" if size_in_bytes < 1024
301
+
302
+ exponent = (Math.log(size_in_bytes) / Math.log(1024)).to_i
303
+ converted_size = size_in_bytes.to_f / 1024 ** exponent
304
+ converted_size = converted_size.round(2)
305
+ return "#{converted_size} #{units[exponent]}"
306
+ end
307
+
308
+ def self.create_timeline(nokogiri_obj, request_session, message, file = nil, location = nil,feeling = nil,filter_type = '-1', **kwargs)
309
+ host = URI("https://mbasic.facebook.com/")
310
+ form = nokogiri_obj.at_xpath("//form[starts-with(@action,'/composer/mbasic')]")
311
+ action = URI.join(host,form['action'])
312
+ formData = {}
313
+ form.css("input[type = 'hidden'][name][value]").each{|i| formData[i['name']] = i['value']}
314
+
315
+ # lanjutkan nanti untuk foto
316
+ unless file.nil?
317
+ file_data = formData.clone
318
+ file_data['view_photo'] = "Submit"
319
+ formFile = request_session.post(action, data = file_data).parse_html.at_xpath("//form[starts-with(@action,'/composer/mbasic')]")
320
+ dataFile = {"add_photo_done"=>"Submit","filter_type"=>filter_type}
321
+ formFile.css("input[type = 'hidden']").each{|i| dataFile[i['name']] = i['value']}
322
+ upload = FbRuby::Utils::upload_photo(request_session, URI.join(host,formFile['action']), file, dataFile)
323
+ imgIds = []
324
+ upload.each do |f|
325
+ action = URI.join(host,f.parse_html.at_css('form')['action'])
326
+ f.parse_html.css("input[type = 'hidden'][name][value]").each do |t|
327
+ if t['name'] == "photo_ids[]"
328
+ imgIds << t['value']
329
+ else
330
+ formData[t['name']] = t['value']
331
+ end
332
+
333
+ end
334
+ end
335
+ formData['photo_ids[]'] = imgIds.join(' , ')
336
+ end
337
+
338
+ unless location.nil?
339
+ lok_data = formData.clone
340
+ lok_data['view_location'] = "Submit"
341
+ formLok = request_session.post(action, data = lok_data).parse_html.at_xpath("//form[starts-with(@action,'/places/selector')]")
342
+ dataLok = {"query"=>location}
343
+ formLok.css("input[type = 'hidden'][name][value]").each{|i| dataLok[i['name']] = i['value']}
344
+ cari = request_session.get(URI.join(host,formLok['action']), params = dataLok).parse_html
345
+ result = cari.at_xpath("//a[starts-with(@href,'/composer/mbasic') and contains(@href,'at=')] | //a[starts-with(@href,'%2Fcomposer%2Fmbasic') and contains(@href,'at=')]")
346
+ raise FbRuby::Exceptions::FacebookError.new("Lokasi dengan nama #{location} tidak di temukan:(") if result.nil?
347
+ formData['at'] = result['href'].match(/at=(\d+)/)[1]
348
+ end
349
+
350
+ unless feeling.nil?
351
+ fel_data = formData.clone
352
+ fel_data['view_minutiae'] = "Submit"
353
+ felUrl = request_session.post(action, data = fel_data).parse_html.at_xpath("//a[starts-with(@href,'/composer/mbasic') and contains(@href,'ogaction')]")
354
+ raise FbRuby::Exceptions::FacebookError.new("Tidak dapat menembahkan feeling:(") if felUrl.nil?
355
+ felForm = request_session.get(URI.join(host,felUrl['href'])).parse_html.at_xpath("//form[starts-with(@action,'/composer/mbasic')]")
356
+ felData = {"mnt_query"=>feeling}
357
+ felForm.css("input[type = 'hidden'][name][value]").each{|i| felData[i['name']] = i['value']}
358
+ cari = request_session.get(URI.join(host,felForm['action']), params = felData).parse_html
359
+ result = cari.at_xpath("//a[starts-with(@href,'/composer/mbasic') and contains(@href,'ogaction')]")
360
+ raise FbRuby::Exceptions::FacebookError.new("Feeling dengan nama #{feeling} tidak di temukan!") if result.nil?
361
+ formData['ogaction'] = result['href'].match(/ogaction=(\d+)/)[1]
362
+ formData['ogphrase'] = feeling
363
+ end
364
+
365
+ formData['view_post'] = "Submit"
366
+ formData['xc_message'] = message
367
+ formData.update(kwargs)
368
+
369
+ begin
370
+ posting = request_session.post(action, data = formData)
371
+ return posting.ok?
372
+ rescue FbRuby::Exceptions::PageNotFound
373
+ return true
374
+ end
375
+ end
376
+
377
+ def self.upload_photo(request_session, upload_url, files, data = {}, headers = {}, separator = '|', default_key = 'file', max_number = 3)
378
+ max_size = 4194304 # Maksimal ukuran foto (4MB)
379
+ support_file = ['.jpg','.png','.webp','.gif','.tiff','.heif','.jpeg']
380
+ photo = []
381
+ reqList = []
382
+
383
+ unless files.kind_of?(Hash)
384
+ number = 0
385
+ files = files.split(separator) if files.kind_of? (String)
386
+ files.each do |f|
387
+ number = 0 if number >= max_number
388
+ number += 1
389
+ photo << {"#{default_key}#{number}"=>f}
390
+ end
391
+ else
392
+ files.each{|k,v| photo << {k=>v}}
393
+ end
394
+
395
+ photo.each_slice(max_number.to_i) do |img|
396
+ mydata = data.clone
397
+
398
+ img.each do |khaneysia|
399
+ key = khaneysia.keys.first
400
+ path = khaneysia.values.first
401
+ ext = File.extname(path)
402
+ raise FbRuby::Exceptions::FacebookError.new("Ukuran file #{File.basename(path)} terlalu besar sehingga file tersebut tidak bisa di upload. File harus berukuran kurang dari #{FbRuby::Utils::convert_file_size(max_size)} :)") if File.size(path) > max_size
403
+ raise FbRuby::Exceptions::FacebookError.new("Hanya bisa mengupload file dengan extensi #{support_file.join(', ')}, tidak bisa mengupload file dengan extensi #{ext}") unless support_file.include? (ext)
404
+ mydata.update({key=>File.new(path,"rb")})
405
+ end
406
+ reqList << request_session.post(upload_url,mydata,headers)
407
+ end
408
+
409
+ return reqList
410
+ end
411
+ end
412
+ end
data/lib/FbRuby.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ #--
4
+ # Copyright 2024 by Rahmat adha
5
+ # All rights reserved.
6
+ # See LICENSE for permissions.
7
+ #++
8
+
9
+ require_relative "FbRuby/user.rb"
10
+ require_relative "FbRuby/posts.rb"
11
+ require_relative "FbRuby/login.rb"
12
+ require_relative "FbRuby/chats.rb"
13
+ require_relative "FbRuby/utils.rb"
14
+ require_relative "FbRuby/groups.rb"
15
+ require_relative "FbRuby/tempmail.rb"
16
+ require_relative "FbRuby/settings.rb"
17
+ require_relative "FbRuby/facebook.rb"
18
+ require_relative "FbRuby/comments.rb"
19
+ require_relative "FbRuby/messenger.rb"
20
+ require_relative "FbRuby/exceptions.rb"
21
+ require_relative "FbRuby/createaccount.rb"
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: FbRuby
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Rahmat adha
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-07-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rest-client
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 2.1.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '2.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 2.1.0
33
+ - !ruby/object:Gem::Dependency
34
+ name: nokogiri
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.15'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.15.3
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.15'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.15.3
53
+ - !ruby/object:Gem::Dependency
54
+ name: yard
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.9'
60
+ type: :development
61
+ prerelease: false
62
+ version_requirements: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '0.9'
67
+ description: Library ini di gunakan untuk scraping web facebook
68
+ email:
69
+ - rahmadadha11@gmail.com
70
+ executables: []
71
+ extensions: []
72
+ extra_rdoc_files: []
73
+ files:
74
+ - Gemfile
75
+ - LICENSE
76
+ - README.md
77
+ - lib/FbRuby.rb
78
+ - lib/FbRuby/chats.rb
79
+ - lib/FbRuby/comments.rb
80
+ - lib/FbRuby/createaccount.rb
81
+ - lib/FbRuby/exceptions.rb
82
+ - lib/FbRuby/facebook.rb
83
+ - lib/FbRuby/groups.rb
84
+ - lib/FbRuby/login.rb
85
+ - lib/FbRuby/messenger.rb
86
+ - lib/FbRuby/posts.rb
87
+ - lib/FbRuby/settings.rb
88
+ - lib/FbRuby/tempmail.rb
89
+ - lib/FbRuby/user.rb
90
+ - lib/FbRuby/utils.rb
91
+ homepage: https://github.com/MR-X-Junior/fbruby
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 3.0.0
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.4.10
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: Facebook Scraper
114
+ test_files: []