ferrumwizard 0.1.4 → 0.2.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: 96cb226b3acf38c7c4b4f8c82665175025effc7d2cbe25d0ae6026e92109c899
4
- data.tar.gz: e31dbbc763e88c553881660d7579117781934e92c2eae10f32fff329da670e09
3
+ metadata.gz: 4b012bc3e8cb9b91311c91eecef6b816cae7bc3d179dc037ac0011373e1d54d2
4
+ data.tar.gz: f0421333194ed8b204ea87beab02314a44f978bbafa28809622b89d1db26bd4f
5
5
  SHA512:
6
- metadata.gz: 0d6f8bd47a33d4d8d3224cc231a993a36c47fa297c91e7b83a3888094de658cf129cc289b001935b442fd96f93103bfe5e214551b5d365de18971330d0e380e2
7
- data.tar.gz: 7fce37cf15f5a493b0261b36eb843ad8087a19b040c14176a0488b0ab0c84220b5e70c12fdb5a962443608092e148b7ecff2832311d7395209115e5cfec5e268
6
+ metadata.gz: b96dd21840cbed0d1253f4795ddf4a4cf36250d5d3736b918c70c7c48ef63d4e8ba31592395cce6de691049a076f41186337a92643e3e1748e122f58edc51905
7
+ data.tar.gz: 39d7c5cc96e6900db1bbfa668ba9b972d49f09147d0fc5b079c34fd40e7235bb256a1713c1bf5725a16c0087dca959d5df8b12b7f10e093891478bf0a33f53f2
checksums.yaml.gz.sig CHANGED
Binary file
data/lib/ferrumwizard.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  # file: ferrumwizard.rb
4
4
 
5
+ require 'yaml'
5
6
  require 'rexle'
6
7
  require 'ferrum'
7
8
 
@@ -10,25 +11,58 @@ class FerrumWizard
10
11
 
11
12
  attr_reader :browser, :links, :radio, :buttons, :js_methods
12
13
 
13
- def initialize(url, headless: true, timeout: 10, debug: false)
14
+ def initialize(url=nil, headless: true, timeout: 10, debug: false)
14
15
 
15
16
  @url, @debug = url, debug
16
17
  @browser = Ferrum::Browser.new headless: headless, timeout: timeout
17
18
  sleep 3
18
-
19
+
19
20
  if url
20
21
  @browser.goto(@url)
21
22
  @browser.network.wait_for_idle
22
- sleep 4
23
+ sleep 4
23
24
  end
24
25
  end
25
-
26
+
26
27
  def inspect()
27
28
  "#<FerrumWizard>"
28
29
  end
29
-
30
+
31
+ # Intended to load all the cookies for a user to login automatically
32
+ #
33
+ # Follow these steps to load the cookies file:
34
+ #
35
+ # 1. launch the Ferrum browser
36
+ # fw = FerrumWizard.new( headless: false, debug: false)
37
+ #
38
+ # 2. load the cookies before you visit the website
39
+ # fw.load_cookies('/tmp/indeed2.txt')
40
+ #
41
+ # 3. visit the website
42
+ # url='https://somewebsite.com'
43
+ # fw.browser.goto(url)
44
+ #
45
+ def load_cookies(filepath)
46
+
47
+ rawcookies = YAML.load(File.read(filepath))
48
+
49
+ rawcookies.each do |h|
50
+
51
+ if @debug then
52
+ puts 'name: ' + h['name']
53
+ puts 'h: ' + h.inspect
54
+ sleep 0.7
55
+ end
56
+
57
+ browser.cookies.set(name: h['name'], value: h['value'],
58
+ domain: h['domain'], expires: h['expires'],
59
+ httponly: h['httpOnly'])
60
+ end
61
+
62
+ end
63
+
30
64
  def login(usernamex=nil, passwordx=nil, username: usernamex, password: passwordx)
31
-
65
+
32
66
  puts 'username: ' + username.inspect if @debug
33
67
 
34
68
  # search for the username input box
@@ -36,23 +70,33 @@ class FerrumWizard
36
70
  puts 'e_username: ' + e_username.inspect if @debug
37
71
  sleep 1
38
72
  # search for the password input box
39
- e_password = @browser.at_xpath('//input[@type="password"]')
73
+ found = @browser.at_xpath('//input[@type="password"]')
74
+
75
+ e_password = if found then
76
+ found
77
+ else
78
+ @browser.xpath('//input').find {|x| x.property(:id) =~ /password/i}
79
+ end
80
+
40
81
  sleep 1
41
-
82
+
42
83
  if username and e_username then
43
84
  puts 'entering the username' if @debug
44
- e_username.focus.type(username)
85
+ e_username.focus.type(username)
45
86
  sleep 1
46
87
  end
47
-
88
+
48
89
  e_password.focus.type(password, :Enter) if e_password
49
-
90
+
50
91
  after_login()
51
-
52
- end
53
-
92
+
93
+ end
94
+
95
+ # login2 is used for websites where the user is presented with the username
96
+ # input box on the first page and the password input box on the next page.
97
+ #
54
98
  def login2(usernamex=nil, passwordx=nil, username: usernamex, password: passwordx)
55
-
99
+
56
100
  puts 'username: ' + username.inspect if @debug
57
101
 
58
102
  # search for the username input box
@@ -60,70 +104,104 @@ class FerrumWizard
60
104
  puts 'e_username: ' + e_username.inspect if @debug
61
105
  sleep 1
62
106
  # search for the password input box
63
-
107
+
64
108
  if username and e_username then
65
109
  puts 'entering the username' if @debug
66
- e_username.focus.type(username, :Enter)
110
+ e_username.focus.type(username, :Enter)
67
111
  sleep 2
68
112
  end
69
113
 
70
- e_password = b.at_xpath('//input[@type="password"]')
114
+ e_password = @browser.at_xpath('//input[@type="password"]')
71
115
  sleep 1
72
-
116
+
73
117
  e_password.focus.type(password, :Enter) if e_password
74
-
118
+
75
119
  after_login()
76
120
 
77
-
78
- end
79
-
121
+
122
+ end
123
+
80
124
  def quit
81
125
  @browser.quit
82
126
  end
83
-
127
+
84
128
  def scan_page()
85
-
86
- @doc = Rexle.new @browser.body
129
+
130
+ @doc = Rexle.new @browser.body
87
131
  fetch_links()
88
- scan_form_elements()
132
+ scan_form_elements()
89
133
  scan_js_links()
90
- self
134
+ @browser.mouse.scroll_to(0, 800)
135
+ self
91
136
  end
92
-
137
+
138
+ # Saves all cookies for a given website into a YAML file
139
+ # see also load_cookies()
140
+ #
141
+ # To use this method follow these steps:
142
+ #
143
+ # 1. launch the web browser through Ferrum
144
+ # fw = FerrumWizard.new(url, headless: false, debug: false)
145
+ #
146
+ # 2. go to the browser and login using your credentials
147
+ # fw.save_cookies(filepath)
148
+ #
149
+ # 3. exit the IRB session
150
+ #
151
+ def save_cookies(filepath=Tempfile.new('ferrum').path)
152
+
153
+ rawcookies = @browser.cookies.all.keys.map do |key|
154
+
155
+ if @debug then
156
+ puts 'key: ' + key.inspect
157
+ sleep 0.5
158
+ end
159
+
160
+ s = @browser.cookies[key].inspect
161
+ a = s.scan(/"([^"]+)"=\>/)
162
+ s2 = s[/(?<=@attributes=).*(?=>)/]
163
+ eval(s2)
164
+
165
+ end
166
+
167
+ File.write filepath, rawcookies.to_yaml
168
+
169
+ end
170
+
93
171
  def submit(h)
94
172
 
95
173
  e = nil
96
-
174
+
97
175
  h.each do |key, value|
98
- e = @browser.xpath('//input').find {|x| x.attribute('name') == key.to_s}
99
- e.focus.type(value)
176
+ e = @browser.xpath('//input').find {|x| x.attribute('name') == key.to_s}
177
+ e.focus.type(value)
100
178
  end
101
-
102
- e.focus.type('', :Enter)
103
-
104
- sleep 4
179
+
180
+ e.focus.type('', :Enter)
181
+
182
+ sleep 4
105
183
  scan_page()
106
-
184
+
107
185
  end
108
186
 
109
187
  def to_rb()
110
188
  end
111
-
189
+
112
190
  private
113
-
191
+
114
192
  def after_login()
115
-
116
- @browser.network.wait_for_idle
193
+
194
+ @browser.network.wait_for_idle
117
195
  sleep 4
118
196
  scan_page()
119
-
197
+
120
198
  @browser.base_url = File.dirname(@browser.url)
121
199
  @browser.mouse.scroll_to(0, 800)
122
200
  self
123
-
201
+
124
202
  end
125
-
126
-
203
+
204
+
127
205
  def fetch_buttons()
128
206
 
129
207
  a2 = @browser.xpath('//input[@type="button"]')
@@ -139,24 +217,24 @@ class FerrumWizard
139
217
  buttons = @buttons
140
218
 
141
219
  names.each do |name|
142
-
220
+
143
221
  define_singleton_method name.to_sym do
144
222
  buttons[name].click
145
223
  @browser.network.wait_for_idle
146
224
  sleep = 1
147
225
  self
148
226
  end
149
-
227
+
150
228
  end
151
229
 
152
230
  end
153
-
231
+
154
232
  def fetch_links()
155
-
233
+
156
234
  all_links = @doc.root.xpath('//a[@href]')
157
-
235
+
158
236
  all_links.each do |x|
159
-
237
+
160
238
  if x.plaintext.empty? then
161
239
  x.text = x.attributes[:href].sub(/\.\w+$/,'')[/([^\/]+)$/].split(/[_]|(?=[A-Z])/).join(' ')
162
240
  else
@@ -164,74 +242,74 @@ class FerrumWizard
164
242
  end
165
243
 
166
244
  end
167
-
245
+
168
246
  valid_links = all_links.reject do |x|
169
-
247
+
170
248
  puts 'x: ' + x.inspect if @debug
171
249
  r = (x.attributes[:target] == '_blank')
172
250
 
173
251
  puts 'r: ' + r.inspect if @debug
174
252
  r
175
-
253
+
176
254
  end
177
-
255
+
178
256
  indices = valid_links.map {|x| all_links.index x}
179
257
 
180
258
  active_links = @browser.xpath('//a[@href]')
181
259
  valid_active_links = indices.map {|n| active_links[n]}
182
-
183
260
 
184
- @links = valid_active_links.flat_map.with_index do |x, i|
261
+
262
+ @links = valid_active_links.flat_map.with_index do |x, i|
185
263
 
186
264
  a = valid_links[i].text.split(/\W+/).map {|label| [label, x]}
187
265
  a << [valid_links[i].text, x]
188
-
266
+
189
267
  puts 'a: ' + a.inspect if @debug
190
268
  a + a.map {|x2, obj| [x2.downcase, obj]}
191
-
269
+
192
270
  end.to_h
193
-
271
+
194
272
  names = @links.keys.map(&:downcase).uniq.select {|x| x =~ /^[\w ]+$/}
195
273
  links = @links
196
-
274
+
197
275
  names.each do |name|
198
-
276
+
199
277
  define_singleton_method name.gsub(/ +/,'_').to_sym do
200
-
278
+
201
279
  links[name].click
202
280
  @browser.network.wait_for_idle
203
-
281
+
204
282
  sleep 1
205
283
  scan_page()
206
284
  self
207
-
285
+
208
286
  end
209
-
287
+
210
288
  end
211
-
289
+
212
290
  end
213
291
 
214
292
  def scan_form_elements()
215
-
293
+
216
294
  # find radio buttons
217
-
295
+
218
296
  a = @browser.xpath('//input[@type="radio"]')
219
297
  h = a.group_by {|x| x.attribute('name')}
220
298
  @radio = h.values
221
299
  define_singleton_method(:on) { @radio[0][0].click; self }
222
300
  define_singleton_method(:off) { @radio[0][1].click; self }
223
-
301
+
224
302
  fetch_buttons()
225
-
303
+
226
304
  end
227
-
305
+
228
306
  def scan_js_links()
229
-
307
+
230
308
  @js_methods = {}
231
309
  b = @browser
232
-
310
+
233
311
  b.xpath('//a').select {|x| x.attribute('href') =~ /^javascript/}.each do |e|
234
-
312
+
235
313
 
236
314
  s = e.attribute('href')[/(?<=^javascript:)[^\(]+/]
237
315
  puts 's: ' + s.inspect if @debug
@@ -241,17 +319,17 @@ class FerrumWizard
241
319
  a << [s.split(/\W+|(?=[A-Z])/).join('_').downcase, s]
242
320
  #@js_methods[s] = a
243
321
 
244
- a.concat a.map {|x, name| [x.downcase, name] }
322
+ a.concat a.map {|x, name| [x.downcase, name] }
245
323
 
246
324
  puts 'a: ' + a.inspect if @debug
247
325
 
248
326
  a.uniq.select {|x, _| x =~ /^[a-z0-9_]+$/}.each do |x, name|
249
-
327
+
250
328
  if @debug then
251
329
  puts 'x: ' + x.inspect
252
330
  puts 'name: ' + name.inspect
253
331
  end
254
-
332
+
255
333
  define_singleton_method(x.to_sym) do |*args|
256
334
  #args = raw_args.map {|x| x[/^[0-9]+$/] ? x.to_i : x}
257
335
  js_method = "%s(%s)" % [name, args.map(&:inspect).join(', ')]
@@ -260,9 +338,9 @@ class FerrumWizard
260
338
  sleep 4
261
339
  self.scan_page()
262
340
  end
263
-
341
+
264
342
  end
265
-
343
+
266
344
  end
267
345
  end
268
346
 
@@ -271,7 +349,7 @@ class FerrumWizard
271
349
  puts 'method_missing: ' + method_name.inspect if @debug
272
350
  node = @browser.at_css '.' + method_name.to_s
273
351
  node.text if node
274
-
275
- end
276
-
352
+
353
+ end
354
+
277
355
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ferrumwizard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - James Robertson
@@ -11,31 +11,31 @@ cert_chain:
11
11
  - |
12
12
  -----BEGIN CERTIFICATE-----
13
13
  MIIEXjCCAsagAwIBAgIBATANBgkqhkiG9w0BAQsFADAsMSowKAYDVQQDDCFnZW1t
14
- YXN0ZXIvREM9amFtZXNyb2JlcnRzb24vREM9ZXUwHhcNMjAwNzMwMTYxOTE5WhcN
15
- MjEwNzMwMTYxOTE5WjAsMSowKAYDVQQDDCFnZW1tYXN0ZXIvREM9amFtZXNyb2Jl
16
- cnRzb24vREM9ZXUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDhNEIG
17
- /Ab4nneih/AQFMcYk76JCiy26Xcy5uxd9ib7Emkj/9sZo6nxuSBaH03+Ixv3jgJs
18
- TxZyaIKRsESFFmupYmKsyatCGGaBEsDb210ZBm313rP2Pk2fGrUtON0CjwJljWxR
19
- 8pHuglEXrGN/XhVicy7sZLJ2nVnvRtyiKi92XmY0S9LaCkWlOx2f3D7yiazkmHh5
20
- 59nHiGNlZ/SOzFrRMdBvkWZYHgqUEBv0KxEuMqW65U4HdlQImcwqu8XOWH9kutof
21
- yyisv03kPqMvrOC8ptG/TieKYK0JuY23gS9MrVxkrf0gX3IQLY21JWG9t9uRImc/
22
- kHC+EJ2rI8HQqcq/v6dndJb6MhYEhj7R5XsZqlfsLFo21FFBAyaPrqPRUstnW5U0
23
- /tCpcuFyZJeRPqQ8LSlRGDuB/TdmV9dF+P5aGS32k9Okf9L6E6x3OGV29eMHSdDt
24
- LOOB8l0EJbNXzpvYW+htziU8TbuzRQU8K7uTeAfpMUg4auPxdVyQpJcQWXcCAwEA
25
- AaOBijCBhzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQU2+nN7PCw
26
- js3NFmK8b17Ji/t+dvwwJgYDVR0RBB8wHYEbZ2VtbWFzdGVyQGphbWVzcm9iZXJ0
14
+ YXN0ZXIvREM9amFtZXNyb2JlcnRzb24vREM9ZXUwHhcNMjIwMzMwMTI1ODU1WhcN
15
+ MjMwMzMwMTI1ODU1WjAsMSowKAYDVQQDDCFnZW1tYXN0ZXIvREM9amFtZXNyb2Jl
16
+ cnRzb24vREM9ZXUwggGiMA0GCSqGSIb3DQEBAQUAA4IBjwAwggGKAoIBgQDROL32
17
+ 3LQKGcDR6x6XFa1US/Vq98DVnMeZHCSKdf471I4gJIOA7sQnrQTB6IZKTxb94Wjr
18
+ OSeGzlJpVq6pa7ltxvb9T7YQVVRrXYMC+u0gD9ukolnkpV/4Rh2/IIMxSNKncoZB
19
+ LKPseizGKlli4gs134gAu3wuWdCC7/UWPG/XyocdJC8tLtf/zi4JuRJTojKqYLOp
20
+ KsP9jHPmGVr81cW8HePmhQ/+LiYlKDE4Fwj4yl16XqhF7/5YOz9e5LOHsMUEord4
21
+ JscQ3GnhMfEXGJpwqCwNEpM3xAwcHp2DDdrwtT36ujSfnTJ3UpUIQUKVehA2i9rm
22
+ uDcDTr1PATGcOMPpExvLZu3a9uC81mj9z+axH5mWQ7jZ92sze79oAQTsMiMyBavJ
23
+ djSpnVBo71PFk8QekgIVVBIzG0iN5zoNUrSthvL/xUWXM6ea015HEDCCIEL417ID
24
+ humVWZyzKf7ITCdZWcxTgTgFfuPMctcICT5u7va+FrycYpdtt8kXvtD3VnkCAwEA
25
+ AaOBijCBhzAJBgNVHRMEAjAAMAsGA1UdDwQEAwIEsDAdBgNVHQ4EFgQUDipvmNU2
26
+ WydgAK8QPGb0vhhoGl0wJgYDVR0RBB8wHYEbZ2VtbWFzdGVyQGphbWVzcm9iZXJ0
27
27
  c29uLmV1MCYGA1UdEgQfMB2BG2dlbW1hc3RlckBqYW1lc3JvYmVydHNvbi5ldTAN
28
- BgkqhkiG9w0BAQsFAAOCAYEAlaYpTQ2vLuKU/nJl1inw9iE9XCwnTmIhmA9lnu1q
29
- QKKCd7Z2PwtkahbDvMVQ347DQZQAanuZmtTPFMc4FDA530qJtwoYk03FTQXBh12M
30
- d4C27VP9BOrUQcxkqtnTo+4Z60taszXqyPsPYU+Fd8AZUPeS5TOYG52OXTQ+q+pO
31
- vNxkRP9oEka81ZrN1y3r3YaFHATZzf4pJo0HupZvMsQwa33/vA+xxxpDeTuWytNN
32
- O0mYbo8Em2LnPnE8ehOnniDGXIIaDO9B1Qbbr0GhNCIWq3JIcbI2IBCKFWA6HyNF
33
- yCdr7ZqPrnxXlhhnTPLFkzR/0+XxpbrdW4zb6uQqX92/tiUqP9uKf5dBEVoCWax/
34
- IWPJE5JXx2iMvE9cWe4bFCUi7cZT7HsL6jkdUWxeTvsfc7XMbE8eWtHHiG6NjeFJ
35
- 7e24hNRMt3t/JE9ogEO4JzFUH2vq2zzR5X9JQqEclWfwHi4cf8bZFJ7spjZQPjSZ
36
- Ok3rs0A+kW4ixAj1rDYuoyG/
28
+ BgkqhkiG9w0BAQsFAAOCAYEAHqRvm6iqjJ+bpEzSSgVmOMOMcgIoN6px1LMVAOmY
29
+ BJpF5F0fJr99thc1EYZJoRTwEcXJYhCTqKg+3xhNKpCzk2qHsaLKYEygPeBpyJOg
30
+ LyfHLrj98QLPYyFzqhWsqZAAAGC9WSF/kBJazpuotU2ec/Xw/e3NPopedV/Zvuhs
31
+ +/OKZWwRez/hg97ckaCYAp/7OdrVhJvR87MaQnN52Uk8OQbuPSyUNQUJ044HWHtu
32
+ lEJjsDetEFhNB69j3wAIMjMEZao29/dZhALbUDp+9ewK7uYbrX9Bo68NX+H+XcCZ
33
+ VFrdrjkyJUOHwSmvjYXN1V0Yz8kVVFU7E+Q4RHL8yAwBv+ynd927HtZVjs+455Pc
34
+ z+9gNpBQVr6LLXLJgJF2pTaIoYhgG6pcoMQHGVoxWdKzvOcl0h1epeJSp/aynX/r
35
+ FK+cyrQNA9DLJYJuz6uO7Z+gXZWjwAO38LUwF01w49asSv/5ZH2HH/EauX5xWpe+
36
+ ry6lYQlb8j50Iys5elAy1p0i
37
37
  -----END CERTIFICATE-----
38
- date: 2020-08-01 00:00:00.000000000 Z
38
+ date: 2022-03-30 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: rexle
@@ -46,7 +46,7 @@ dependencies:
46
46
  version: '1.5'
47
47
  - - ">="
48
48
  - !ruby/object:Gem::Version
49
- version: 1.5.7
49
+ version: 1.5.14
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
@@ -56,29 +56,29 @@ dependencies:
56
56
  version: '1.5'
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: 1.5.7
59
+ version: 1.5.14
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: ferrum
62
62
  requirement: !ruby/object:Gem::Requirement
63
63
  requirements:
64
- - - ">="
65
- - !ruby/object:Gem::Version
66
- version: 0.9.0
67
64
  - - "~>"
68
65
  - !ruby/object:Gem::Version
69
- version: '0.9'
66
+ version: '0.11'
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0.11'
70
70
  type: :runtime
71
71
  prerelease: false
72
72
  version_requirements: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: 0.9.0
77
74
  - - "~>"
78
75
  - !ruby/object:Gem::Version
79
- version: '0.9'
76
+ version: '0.11'
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: '0.11'
80
80
  description:
81
- email: james@jamesrobertson.eu
81
+ email: digital.robertson@gmail.com
82
82
  executables: []
83
83
  extensions: []
84
84
  extra_rdoc_files: []
@@ -103,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
103
  - !ruby/object:Gem::Version
104
104
  version: '0'
105
105
  requirements: []
106
- rubygems_version: 3.0.3
106
+ rubygems_version: 3.2.22
107
107
  signing_key:
108
108
  specification_version: 4
109
109
  summary: Makes web scraping easier using the Ferrum gem.
metadata.gz.sig CHANGED
@@ -1,5 +1,4 @@
1
- �w�k���? k� ^M��K����|
2
- ���L9�b�qs���Z�#����
3
- 3y��,c��;��? 8Ŧ��B)dm2����05x���wu��74p�9wfɈ�HU
4
- ��e�=|���R���ʉ+ km��
5
- �L]Ѩ�D#��IKSs^�QE��G3�h��3L0���Q�!�<ʛx�r��R���:�X:b@+��%3;�]x�+��f/L��D�hv͙�P��c��i��#l�ْ��x�_�4QwUל^���}Y؍�A�"g�b�F�Mr��N?N#�52��w�̳V��c\^�X�<=�����
1
+ in��woȾ�t�58�w�B'�??
2
+ U�щ�
3
+ 1k�<�(
4
+ �`�V�q0��@�� Q �/�b�@��o����� fZ�F���[Q��*�*�M�7��퉰N3?��q��R� f)���9�W\�