ubi 0.0.5 → 0.0.7

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
  SHA1:
3
- metadata.gz: 9a9073e8291c6e092b1f3585191aa4c2c91e524d
4
- data.tar.gz: ceee4c37c817147c7f4a8f0d4627b3c5108ccbf5
3
+ metadata.gz: 7fdab4d682e936e1a133a4dc7be3d8b8e94e5911
4
+ data.tar.gz: a159c4494b80dd627fe1e977beea018dc85dcd4f
5
5
  SHA512:
6
- metadata.gz: 796e569c777e65d7a8aca7ceb5cb9579fc5f52bfdef22a16cb8b54ec2b48854e0ee207c66d90e2c6bead5fa5622ae328760cf8bad111859059695002871ad6c3
7
- data.tar.gz: 7791238d12cf001ed5e2d36d86645e42debe21f3a968ed9cc3c21e18636dcba4c1c44ce7714e526f78330827cb812826c3d261f4542a17729799452b75aa996e
6
+ metadata.gz: 2dfc19bf88633329bbe9ed38ff7181c5f9d772217319b03d4dc1c8ef8ef7bd5375590acf7ef3545ed6472d62b7a2e7b1ee1646fe1be1f1bd399db6ae90102a8f
7
+ data.tar.gz: f921d122f54a59b8997e36decfdd3072fff344d1b0d8b241fe0cd797e06427a8dbc6524d08c2ab3035ead12f9738ac0d4dbaf649663b7591d578d37791fb69cc
data/Rakefile CHANGED
@@ -6,4 +6,4 @@ require 'rubocop/rake_task'
6
6
  RSpec::Core::RakeTask.new
7
7
  RuboCop::RakeTask.new
8
8
 
9
- task default: [:rubocop, :spec]
9
+ task default: [:spec]
data/lib/ubi.rb CHANGED
@@ -1,15 +1,14 @@
1
- require 'pry'
2
- require 'thor'
3
- require 'net/http'
4
- require 'net/https'
1
+ # require 'net/http'
2
+ # require 'net/https'
5
3
  require 'uri'
6
- require 'open-uri'
4
+ # require 'open-uri'
7
5
  require 'json'
8
6
  require 'phonelib'
9
7
  require 'nokogiri'
10
8
  require 'active_model'
11
9
  require 'active_support'
12
10
  require 'active_support/core_ext/hash'
11
+ require 'public_suffix'
13
12
  require 'ubi/version'
14
13
 
15
14
  # Ubiquous getters
@@ -1,42 +1,54 @@
1
- require 'polipus'
1
+ require 'retriever'
2
2
 
3
3
  module Ubi
4
4
  # Base for araneas (spiders)
5
5
  class Aranea
6
6
  OPTIONS = {
7
- workers: 3,
8
7
  user_agent: "Ubi v#{Ubi::VERSION}",
9
- depth_limit: 1
10
-
11
- # storage: MemoryStore
8
+ depth_limit: 3,
9
+ logger: Logger.new(STDOUT),
10
+ # redis_options: {
11
+ # host: 'localhost',
12
+ # db: 5,
13
+ # driver: 'hiredis'
14
+ # },
12
15
  }
13
- attr_accessor :thema, :url, :datum
16
+ attr_accessor :thema, :url, :datum, :html, :text, :opts
14
17
 
15
18
  def initialize(thema, url, opts = {})
16
19
  @thema = thema
17
20
  @url = url
18
- @opts = opts
21
+ @opts = OPTIONS.merge(opts)
22
+ @datum = []
23
+ @html = []
24
+ @text = ''
19
25
  end
20
26
 
21
27
  delegate :name, to: :thema
22
28
 
23
29
  def crawl!
24
- Polipus.crawler(name, url, OPTIONS.merge(@opts)) do |crawler|
25
- # In-place page processing
26
- crawler.on_page_downloaded do |page|
27
- # A nokogiri object
28
- puts "'#{page.doc.css('title').text}' (#{page.url})"
29
- end
30
+ @last_run = Time.now
31
+
32
+ puts "Crawler start #{name} #{url}"
33
+ Retriever::PageIterator.new(url, opts) do |page|
34
+ parse page.source
35
+ p [page.title, page.h1, page.h2]
30
36
  end
31
37
  end
32
38
 
33
- def parser(chunk)
34
- Nokogiri::HTML(chunk)
39
+ def parse(chunk)
40
+ @datum << chunk
41
+ @html << Nokogiri::HTML(chunk)
42
+ @text << html.last.text
43
+ end
44
+
45
+ def work
46
+ crawl! unless @last_run
47
+ true
35
48
  end
36
49
 
37
- def datum
38
- crawl! unless @datum
39
- @datum
50
+ def to_s
51
+ "#{thema} html: #{html.size} txt: #{text.size}"
40
52
  end
41
- end
42
- end
53
+ end # Aranea
54
+ end # Ubi
@@ -5,10 +5,11 @@ module Ubi
5
5
  attr_accessor :thema
6
6
 
7
7
  def initialize(query)
8
- @thema = Thema.new(query)
9
- start_with_search
10
- social_search
8
+ @thema = Thema.new(query[:name], query[:urls])
9
+ query[:urls] ? fetch_pages : start_with_search
10
+ social_search if query[:mail]
11
11
  other_search
12
+ do_the_twist
12
13
  end
13
14
 
14
15
  def start_with_search
@@ -23,6 +24,16 @@ module Ubi
23
24
  def other_search
24
25
  end
25
26
 
27
+ def fetch_pages
28
+ thema.araneas.each(&:work)
29
+ end
30
+
31
+ def do_the_twist
32
+ thema.araneas.each { |a| thema.try_datum(a) }
33
+ # pp thema.spec
34
+ # binding.pry if binding.respond_to?(:pry)
35
+ end
36
+
26
37
  delegate :spec, to: :thema
27
38
  end
28
39
  end
@@ -1,3 +1,5 @@
1
+ require 'pry'
2
+ require 'thor'
1
3
  require 'paint'
2
4
 
3
5
  module Ubi
@@ -14,7 +16,19 @@ module Ubi
14
16
  LONG
15
17
  option :address, type: :string # 'Subject\'s address'
16
18
  def find(name)
17
- Ubi::Artifex.new(name).spec
19
+ Ubi::Artifex.new(name: name).spec
20
+ end
21
+
22
+ # desc 'init', 'creates settings on ~'
23
+ desc 'site', 'Lookup in site'
24
+ long_desc <<-LONG
25
+
26
+ Find data on URL
27
+
28
+ LONG
29
+ option :address, type: :string # 'Subject\'s address'
30
+ def site(*url)
31
+ Ubi::Artifex.new(urls: url).spec
18
32
  end
19
33
  end
20
34
  end
@@ -24,6 +24,11 @@ module Ubi
24
24
  format
25
25
  end
26
26
 
27
+ def ==(other)
28
+ return unless other.respond_to?(:text)
29
+ text == other.text
30
+ end
31
+
27
32
  class << self
28
33
  #
29
34
  # Account for memorias
@@ -37,6 +42,7 @@ module Ubi
37
42
  def extract_text(datum)
38
43
  case datum
39
44
  when String then datum
45
+ when Ubi::Aranea then datum.text
40
46
  when Nokogiri::HTML then datum.data.text
41
47
  # when PDF / DOC / IMG (tesseract it =) then datum.data.text
42
48
  else fail "Can't parse `#{datum.class}`"
@@ -79,11 +85,6 @@ module Ubi
79
85
  def plural
80
86
  "#{key}s"
81
87
  end
82
-
83
- def ==(other)
84
- return unless other.respond_to?(:key)
85
- key == other.key
86
- end
87
88
  end
88
89
  end
89
90
  end
@@ -38,7 +38,8 @@ module Ubi
38
38
 
39
39
  def fetch_possible
40
40
  parse_zip
41
- @region = clean.scan(/\W([A-Z]{2})\W/).first.first
41
+ @region = clean.scan(/\W([A-Z]{2})\W/).first
42
+ @region = @region.first if @region
42
43
  @number = clean.scan(/\d+/).join(' ')
43
44
  end
44
45
  #
@@ -53,7 +54,7 @@ module Ubi
53
54
  end
54
55
 
55
56
  def format(location = :br)
56
- text.sub(*self.class.formats[location])
57
+ text #.sub(*self.class.formats[location])
57
58
  end
58
59
 
59
60
  class << self
@@ -85,7 +86,7 @@ module Ubi
85
86
  end
86
87
 
87
88
  def regex(hint)
88
- /((?:#{REGEXES[hint][:prefix].join('|')}).*)/i
89
+ /(\b(?:#{REGEXES[hint][:prefix].join('|')})\s.*)\b/i
89
90
  end
90
91
 
91
92
  def plural
@@ -2,6 +2,16 @@ module Ubi
2
2
  module Memoria
3
3
  # An Electronic Mail
4
4
  class Email < Base
5
+ #
6
+ # Clean up regex on init
7
+ #
8
+ def initialize(text, _hint = nil, opts = {})
9
+ text = text.downcase.gsub(/^\(|\.$/, '')
10
+ @text = text
11
+ # @addr = text
12
+ @opts = opts
13
+ end
14
+
5
15
  #
6
16
  #
7
17
  # Class methods
@@ -15,7 +25,7 @@ module Ubi
15
25
  ([a-z0-9!#$%&'*+/=?^_`{|}~-]+
16
26
  (?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@
17
27
  (?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+
18
- (?:[a-z0-9-]*[a-z0-9])?)
28
+ (?:[a-z0-9-]*[a-z0-9])?)(?:\W|\s|$)
19
29
  }x
20
30
  end
21
31
  end
@@ -0,0 +1,25 @@
1
+ module Ubi
2
+ module Memoria
3
+ # Show me your papers!
4
+
5
+ class Image < Base
6
+ attr_reader :size
7
+
8
+ def parser
9
+ @size = []
10
+ end
11
+ #
12
+ #
13
+ # Class methods
14
+ #
15
+ class << self
16
+ #
17
+ # Email regex
18
+ #
19
+ def regex(_hint)
20
+ /\.(jpg|png|svg)$/i
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -9,16 +9,20 @@ module Ubi
9
9
  end
10
10
 
11
11
  def format
12
+ number && number.e164
13
+ end
14
+
15
+ def national
12
16
  number && number.national
13
17
  end
14
18
 
15
- def rfc
19
+ def international
16
20
  number && number.international
17
21
  end
18
22
 
19
23
  class << self
20
24
  # http://rubular.com/r/tEHB6KcZzk
21
- def regex(hint = nil)
25
+ def regex(_hint = nil)
22
26
  /
23
27
  (?:^|\s)((?:\+\(?\d{1,3}\W)?[\._\-\/\s]*
24
28
  \(?\s*?\d{2,3}\s*?\)?[\._\-\/\s]*\d{3,5}
@@ -2,10 +2,27 @@ module Ubi
2
2
  module Memoria
3
3
  # A site, url and title?
4
4
  class Site < Base
5
+ attr_accessor :link, :uri
6
+
7
+ def initialize(text, _hint = nil, opts = {})
8
+ text = text.downcase.gsub(/^\(|\.$/, '')
9
+ text = "http://#{text}" unless text =~ %r{^\w{3,5}://}
10
+ @text = text
11
+ @link = tld_parser
12
+ @opts = opts
13
+ end
14
+
15
+ def tld_parser
16
+ @uri = URI.parse(text)
17
+ @link = PublicSuffix.parse(uri.host)
18
+ rescue PublicSuffix::DomainInvalid
19
+ nil
20
+ end
21
+
5
22
  #
6
23
  # Prefix http:// if there isn't one defined
7
24
  def format
8
- text =~ /^http/ ? text : "http://#{text}"
25
+ text
9
26
  end
10
27
 
11
28
  class << self
@@ -11,7 +11,7 @@ module Ubi
11
11
  }
12
12
  end
13
13
 
14
- def regex
14
+ def regex(url = nil)
15
15
  %r{https?://(?:\w+\.)*(#{url}/.*)}
16
16
  end
17
17
  end
@@ -11,7 +11,7 @@ module Ubi
11
11
  }
12
12
  end
13
13
 
14
- def regex
14
+ def regex(url)
15
15
  %r{https?://(?:\w+\.)*(#{url}/.*)}
16
16
  end
17
17
  end
@@ -12,6 +12,8 @@ module Ubi
12
12
  def initialize(name, urls = [], opts = {})
13
13
  @name = name
14
14
  @urls = urls
15
+ @name ||= urls.first.split('.').first.gsub(%r{^\w+://}, '')
16
+ @urls = @urls.map { |url| url =~ %r{://} ? url : "http://#{url}" }
15
17
  @opts = opts
16
18
  @cache = Ubi.memorias.reduce({}) { |a, e| a.merge(e => opts[e]) }
17
19
  reduce_names
@@ -22,36 +24,38 @@ module Ubi
22
24
  end
23
25
 
24
26
  def reduce_names
27
+ return unless name
25
28
  @ascii = name.mb_chars.downcase
26
29
  @downcase = name.mb_chars.downcase
27
30
  @clean = @downcase.gsub(/\W/, ' ')
28
31
  end
29
32
 
33
+ # Define memorias on thema
30
34
  Ubi.memorias.each do |memoria|
31
35
  define_method memoria.plural do
32
- instance_variable_get('@' + memoria.plural) ||
33
- instance_variable_set('@' + memoria.plural, [])
36
+ instance_variable_get("@#{memoria.plural}") ||
37
+ instance_variable_set("@#{memoria.plural}", [])
34
38
  end
35
39
  end
36
40
 
37
- def [](arg)
38
- @cache[arg]
39
- end
40
-
41
41
  def spec
42
42
  puts self
43
43
  Ubi.memorias.each do |memoria|
44
- print Paint[memoria.name, :black]
45
- puts self[memoria.key]
44
+ d = send(memoria.plural)
45
+ puts Paint["#{memoria.name} (#{d.size})", :black]
46
+ puts d
46
47
  end
47
48
  end
48
49
 
49
- def try_consultor(a)
50
- a = a.new(self)
50
+ def try_datum(a)
51
51
  Ubi.memorias.each do |m|
52
- puts Paint["Trying to find #{m} in #{a.class}", :green]
53
- @cache[m] = matches = m.parse(a.datum)
54
- puts matches if matches && !matches.empty?
52
+ print Paint["Trying to find #{m} in page ", :green]
53
+ matches = m.parse(a)
54
+ puts Paint[matches.size, :black]
55
+ next unless matches && !matches.empty?
56
+ matches.each do |match|
57
+ send(m.plural) << match unless send(m.plural).include?(match)
58
+ end
55
59
  end
56
60
  end
57
61
 
@@ -1,4 +1,4 @@
1
1
  # :nodoc:
2
2
  module Ubi
3
- VERSION = '0.0.5'
3
+ VERSION = '0.0.7'
4
4
  end
@@ -38,5 +38,4 @@ RSpec.configure do |config|
38
38
  # # VCR.use_cassette(vcr) { example.run }
39
39
  # puts Paint[" [#{Time.now - t}s]", :black]
40
40
  # end
41
- # config.before(:each) { Polipus::SignalHandler.disable }
42
41
  end
@@ -8,7 +8,7 @@ describe Aranea do
8
8
 
9
9
  it 'should use only one url' do
10
10
  aranea = Thema.new('Rock', ['r1.com', 'r2.com']).araneas.first
11
- expect(aranea.url).to eq('r1.com')
11
+ expect(aranea.url).to eq('http://r1.com')
12
12
  end
13
13
 
14
14
  # it 'should crawl a domain VCR ruby-lang', :vcr do
@@ -12,6 +12,24 @@ describe Memoria::Email do
12
12
  end
13
13
  end
14
14
 
15
+ describe 'parsed' do
16
+ def parse(site)
17
+ Memoria::Email.parse(site).first.to_s
18
+ end
19
+
20
+ describe 'http' do
21
+ let(:parsed) { 'hoho@fubah.com' }
22
+
23
+ it { expect(parse('hoho@fubah.com')).to eq(parsed) }
24
+ it { expect(parse('hoho@fubah.com.')).to eq(parsed) }
25
+ it { expect(parse('"hoho@fubah.com"')).to eq(parsed) }
26
+ it { expect(parse('( hoho@fubah.com )')).to eq(parsed) }
27
+ it { expect(parse('(hoho@fubah.com)')).to eq(parsed) }
28
+ it { expect(parse('(hoho@fubah.com.)')).to eq(parsed) }
29
+ it { expect(parse('hoho@fubah.com/')).to eq(parsed) }
30
+ end
31
+ end
32
+
15
33
  describe 'valid' do
16
34
  %w(
17
35
  foo@foo.com
@@ -1,6 +1,10 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Memoria::Phone do
4
+ def parse(phone)
5
+ Memoria::Phone.parse(phone).first.to_s
6
+ end
7
+
4
8
  describe 'simple test' do
5
9
  subject { Memoria::Phone.new('+551112345678') }
6
10
 
@@ -29,11 +33,11 @@ describe Memoria::Phone do
29
33
  end
30
34
  end
31
35
 
32
- describe 'parsed landlines' do
33
- def parse(phone)
34
- Memoria::Phone.parse(phone).first.rfc
35
- end
36
+ describe 'random tests' do
37
+ it { expect(parse('(16) 3919-0723')).to eq('+551639190723') }
38
+ end
36
39
 
40
+ describe 'parsed landlines' do
37
41
  let(:parsed) { '+1112345678' }
38
42
 
39
43
  load_fixture('phone.txt').each_line do |l|
@@ -44,11 +48,7 @@ describe Memoria::Phone do
44
48
  end
45
49
 
46
50
  describe 'parsed mobiles txt' do
47
- def parse(phone)
48
- Memoria::Phone.parse(phone).first.rfc
49
- end
50
-
51
- let(:parsed) { '+55 11 99814-5678' }
51
+ let(:parsed) { '+5511998145678' }
52
52
 
53
53
  load_fixture('mobile.txt').each_line do |l|
54
54
  it "Should parse phone #{l}" do
@@ -58,11 +58,7 @@ describe Memoria::Phone do
58
58
  end
59
59
 
60
60
  describe 'parsed mobiles' do
61
- def parse(phone)
62
- Memoria::Phone.parse(phone).first.rfc
63
- end
64
-
65
- let(:parsed) { '+55 11 98234-5678' }
61
+ let(:parsed) { '+5511982345678' }
66
62
 
67
63
  it { expect(parse('11982345678')).to eq(parsed) }
68
64
  it { expect(parse('11 982345678')).to eq(parsed) }
@@ -8,7 +8,7 @@ describe Memoria::Site do
8
8
  it { is_expected.to be_an Array }
9
9
 
10
10
  it 'should have text reader method' do
11
- expect(subject[0].text).to eq('somesite.com')
11
+ expect(subject[0].text).to eq('http://somesite.com')
12
12
  end
13
13
  end
14
14
 
@@ -21,6 +21,8 @@ describe Memoria::Site do
21
21
  let(:parsed) { 'http://fubah.com' }
22
22
 
23
23
  it { expect(parse('fubah.com')).to eq(parsed) }
24
+ it { expect(parse('fubah.com.')).to eq(parsed) }
25
+ it { expect(parse('(fubah.com.)')).to eq(parsed) }
24
26
  it { expect(parse('@fubah.com')).to eq(parsed) } # 'http://@fubah.com') }
25
27
  it { expect(parse('fu@fubah.com')).to eq(parsed) } # 'http://fu@fubah.com') }
26
28
  it { expect(parse('http://fubah.com')).to eq(parsed) }
@@ -6,7 +6,7 @@ describe Thema do
6
6
  end
7
7
 
8
8
  it 'should accept urls too' do
9
- expect(Thema.new('Hard Rock', ['hr.com']).urls).to include('hr.com')
9
+ expect(Thema.new('Hard Rock', ['hr.com']).urls).to include('http://hr.com')
10
10
  end
11
11
 
12
12
  it 'should convert urls to aranea' do
@@ -26,9 +26,12 @@ Gem::Specification.new do |s|
26
26
  s.add_dependency 'paint'
27
27
  s.add_dependency 'phonelib'
28
28
  # s.add_dependency 'addressie', '~> 0.0.0'
29
- s.add_dependency 'polipus'
30
29
  s.add_dependency 'nokogiri'
30
+ s.add_dependency 'whatlanguage'
31
31
  s.add_dependency 'geopolitical'
32
+ s.add_dependency 'public_suffix'
33
+ s.add_dependency 'rubyretriever'
34
+ s.add_dependency 'charlock_holmes'
32
35
 
33
36
  s.add_development_dependency 'vcr'
34
37
  s.add_development_dependency 'rspec'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ubi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcos Piccinini
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-08-22 00:00:00.000000000 Z
11
+ date: 2015-08-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thor
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: polipus
56
+ name: nokogiri
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: nokogiri
70
+ name: whatlanguage
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -94,6 +94,48 @@ dependencies:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: public_suffix
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: rubyretriever
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: charlock_holmes
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: vcr
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +213,7 @@ files:
171
213
  - lib/ubi/memorias/address.rb
172
214
  - lib/ubi/memorias/document.rb
173
215
  - lib/ubi/memorias/email.rb
216
+ - lib/ubi/memorias/image.rb
174
217
  - lib/ubi/memorias/phone.rb
175
218
  - lib/ubi/memorias/site.rb
176
219
  - lib/ubi/memorias/social.rb
@@ -212,7 +255,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
212
255
  version: '0'
213
256
  requirements: []
214
257
  rubyforge_project:
215
- rubygems_version: 2.4.5
258
+ rubygems_version: 2.4.5.1
216
259
  signing_key:
217
260
  specification_version: 4
218
261
  summary: Find devices connected in LAN