polar_express 0.0.3.alpha → 0.0.4.alpha

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,7 @@ require "polar_express/polar_express"
3
3
  require "polar_express/tracker"
4
4
  require "polar_express/dhl"
5
5
  require "polar_express/gls"
6
+ require "polar_express/hermes"
6
7
  require "polar_express/shipping_info"
7
8
 
8
9
  module PolarExpress
@@ -11,4 +12,6 @@ module PolarExpress
11
12
  require 'nokogiri'
12
13
  require 'date'
13
14
  require 'json'
14
- end
15
+ require 'net/http'
16
+ require "use_ssl_certs.rb"
17
+ end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  module PolarExpress
2
3
  module DHL
3
4
  class Tracker
@@ -13,7 +14,7 @@ module PolarExpress
13
14
  end
14
15
  private
15
16
  def tracking_url
16
- "http://nolp.dhl.de/nextt-online-public/set_identcodes.do?lang=en&idc=#{@number}"
17
+ "http://nolp.dhl.de/nextt-online-public/set_identcodes.do?lang=de&idc=#{@number}"
17
18
  end
18
19
  def page
19
20
  @page ||= Nokogiri::HTML open tracking_url
@@ -23,42 +24,109 @@ module PolarExpress
23
24
  end
24
25
  def timeline
25
26
  page.css("tr#toggle-#{@number}_1 table tbody tr").map do |tr|
27
+ if tr.css('.retoure_left').length > 0 then
28
+ status_text = tr.css('td.retoure_right div').last.text.strip
29
+ else
30
+ status_text = tr.css('td.status').text.strip
31
+ end
26
32
  {
27
33
  date: DateTime.parse(get_date(tr.css('td.overflow').text)),
28
34
  city: (city = tr.css('td.location').text.strip) == '--' ? nil : city,
29
- status: text_to_status(status_text = tr.css('td.status').text.strip),
35
+ status: text_to_status(status_text),
30
36
  text: status_text
31
37
  }
32
38
  end
33
39
  end
34
- # TODO: look for better status names
35
40
  def text_to_status(text)
36
- case text
37
- when /The recipient has picked up the shipment from/
38
- :delivered
39
- when /The shipment has been successfully delivered/
40
- :delivered
41
- when /The shipment is on its way to the postal retail outlet/
42
- :in_delivery_vehicle_to_retail_outler
43
- when /The shipment has been delivered for pick-up at the/
44
- :waiting_for_pick_up_in_retail_office
45
- when /The shipment has been loaded onto the delivery vehicle/
46
- :in_delivery_vehicle
47
- when /The shipment has been processed in the destination parcel center/
48
- :destination_parcel_center
49
- when /The shipment has been processed in the parcel center of origin/
50
- :origin_parcel_center
51
- when /The instruction data for this shipment have been provided by the sender to DHL electronically/
52
- :shipping_instructions_received
53
- when /A .+ attempt at delivery is being made/
54
- :new_delivery_attempt
55
- else
56
- :other
41
+ match = Statuses.find do |status, regexes|
42
+ regexes.find { |regex| text =~ regex }
57
43
  end
44
+ (match && match.first) || :other
58
45
  end
59
46
  def get_date(str)
60
47
  str.scan(/[\d]{1,2}\.[\d]{1,2}\.[\d]{4} [\d]{1,2}:[\d]{1,2}/).flatten.first
61
48
  end
62
49
  end
50
+ Statuses = {
51
+ :delivery_failed => [
52
+ /Aufgrund eines Adressfehlers konnte die Sendung nicht zugestellt werden/,
53
+ /Aus unvorhersehbaren Gründen konnte die Sendung nicht zugestellt werden/,
54
+ /Der Empfänger wurde nicht angetroffen. Die Sendung konnte nicht zugestellt werden und wird gelagert. Der Empfänger wurde benachrichtigt/,
55
+ /Die Sendung konnte heute nicht zugestellt werden/,
56
+ /Die Sendung konnte nicht zugestellt werden, der Empfänger wurde benachrichtigt/,
57
+ /Empfangsadresse bei Zustellversuch geschlossen/,
58
+ /Erfolgloser Zustellversuch, Empfänger nicht zu Hause/,
59
+ /Zustellung in Packstation nicht möglich/,
60
+ ],
61
+ :delivery_succeeded => [
62
+ /Der Empfänger hat die Sendung aus der PACKSTATION abgeholt/,
63
+ /Der Empfänger hat die Sendung in der Filiale abgeholt/,
64
+ /Die Sendung wurde abgeholt/,
65
+ /Die Sendung wurde dem Empfänger per vereinfachter Firmenzustellung ab Zustellbasis zugestellt/,
66
+ /Die Sendung wurde erfolgreich zugestellt/,
67
+ /Die Überweisung des Nachnahme-Betrags an den Zahlungsempfänger ist erfolgt/,
68
+ ],
69
+ :destination_parcel_center => [
70
+ /Die Sendung wurde im Ziel-Paketzentrum bearbeitet/,
71
+ /Die Sendung wurde innerhalb des Ziel-Paketzentrums weitergeleitet/,
72
+ ],
73
+ :in_delivery_vehicle => [
74
+ /Die Sendung befindet sich auf dem Weg zur PACKSTATION/,
75
+ /Die Sendung wird zur Abholung in die Filiale (.+) gebracht/,
76
+ /Die Sendung wurde in das Zustellfahrzeug geladen/,
77
+ /in Auslieferung durch Kurier/,
78
+ ],
79
+ :in_shipment => [
80
+ /Die Auslandssendung wurde im Export-Paketzentrum bearbeitet/,
81
+ /Die Sendung aus dem Ausland ist im Import-Paketzentrum eingetroffen/,
82
+ /Die Sendung hat das Import-Paketzentrum im Zielland verlassen/,
83
+ /Die Sendung ist im Zielland eingetroffen/,
84
+ /Die Sendung wird ins Zielland transportiert und dort an die Zustellorganisation (.+) übergeben/,
85
+ /Die Sendung wird ins Zielland transportiert und dort an die Zustellorganisation übergeben/,
86
+ /Die Sendung wird ins Zielland transportiert/,
87
+ /Die Sendung wird zur Verzollung im Zielland vorbereitet/,
88
+ /Die Sendung wurde dem Empfänger per vereinfachter Firmenzustellung ab Paketzentrum zugestellt/,
89
+ /Die Sendung wurde vom Absender in der Filiale eingeliefert/,
90
+ /Geplant für Zustellung/,
91
+ /Sendung hat den Abholstandort verlassen/,
92
+ /Sendung hat die Umschlagbasis verlassen/,
93
+ /Sendung ist im Zustellstandort eingegangen/,
94
+ /Sendung ist in den Abholstandort eingegangen/,
95
+ /Sendung ist in der Umschlagbasis eingegangen/,
96
+ /Sendung wurde weiter bearbeitet/,
97
+ /Verzollung abgeschlossen/,
98
+ ],
99
+ :new_delivery_attempt => [
100
+ /Es erfolgt ein 2. Zustellversuch/,
101
+ ],
102
+ :on_hold => [
103
+ /Die für die Auslieferung der Sendung erforderlichen Daten liegen nicht vor. Die Sendung wird zurückgestellt/,
104
+ /Die Sendung wird vorübergehend bis zur Zustellung in der Zustellbasis gelagert/,
105
+ /Die Sendung wurde beschädigt und wird zur Nachverpackung in das Paketzentrum zurückgesandt/,
106
+ /Die Sendung wurde fehlgeleitet und konnte nicht zugestellt werden. Die Sendung wird umadressiert und an den Empfänger weitergeleitet/,
107
+ /Die Sendung wurde zurückgestellt. Die Zustellung erfolgt voraussichtlich am nächsten Werktag/,
108
+ /Nicht durch DHL beeinflussbare Verzögerung in der Verzollung/,
109
+ ],
110
+ :on_hold_at_depot => [
111
+ /Sendung nach Zustellversuch zurück in DHL Station/,
112
+ /Sendung zur Aufbewahrung in der Station/,
113
+ ],
114
+ :origin_parcel_center => [
115
+ /Die Auslands-Sendung wurde im Start-Paketzentrum bearbeitet/,
116
+ /Die Sendung wurde im Paketzentrum bearbeitet/,
117
+ /Die Sendung wurde im Start-Paketzentrum bearbeitet/,
118
+ ],
119
+ :return_shipment => [
120
+ /Die Sendung wurde nicht abgeholt und wird zurückgesandt/,
121
+ /Rücksendung eingeleitet/,
122
+ ],
123
+ :shipping_instructions_received => [
124
+ /Die Auftragsdaten zu dieser Sendung wurden vom Absender elektronisch an DHL übermittelt/,
125
+ ],
126
+ :waiting_for_pickup_by_customer => [
127
+ /Die Sendung liegt in der Filiale zur Abholung bereit/,
128
+ /Die Sendung liegt in der PACKSTATION zur Abholung bereit/,
129
+ ],
130
+ }.freeze
63
131
  end
64
132
  end
@@ -1,3 +1,4 @@
1
+ # encoding: utf-8
1
2
  module PolarExpress
2
3
  module GLS
3
4
  class Tracker
@@ -12,7 +13,7 @@ module PolarExpress
12
13
  end
13
14
  private
14
15
  def tracking_url
15
- "https://gls-group.eu/app/service/open/rest/EU/en/rstt001?match=#{@number}&caller=witt002Scheme"
16
+ "https://gls-group.eu/app/service/open/rest/EU/de/rstt001?match=#{@number}&caller=witt002Scheme"
16
17
  end
17
18
  def page
18
19
  @page ||= JSON.parse(open(tracking_url).read)
@@ -29,33 +30,98 @@ module PolarExpress
29
30
  def timeline
30
31
  history.each_with_index.map do |event, index|
31
32
  # GLS specific logic
32
- next if event[:text] == 'Inbound to GLS location to document physical hand over'
33
- if event[:text] == 'Inbound to GLS location' and history[index-1][:text] == 'Outbound from GLS location'
34
- event[:text] = 'The shipment has been processed in the destination parcel center'
35
- end
33
+ #next if event[:text] == 'Inbound to GLS location to document physical hand over'
36
34
  event[:status] = text_to_status(event[:text])
37
35
  event
38
36
  end.compact
39
37
  end
40
- # TODO: look for better status names
41
38
  def text_to_status(text)
42
- case text
43
- when /Delivered/
44
- :delivered
45
- when /Out for delivery on GLS vehicle/
46
- :in_delivery_vehicle
47
- when /The shipment has been processed in the destination parcel center/
48
- :destination_parcel_center
49
- when /Inbound to GLS location/
50
- :origin_parcel_center
51
- when /Outbound from GLS location/
52
- :in_shipment
53
- when /Information transmitted, no shipment available now/
54
- :shipping_instructions_received
55
- else
56
- :other
39
+ match = Statuses.find do |status, regexes|
40
+ regexes.find { |regex| text =~ regex }
57
41
  end
42
+ (match && match.first) || :other
58
43
  end
59
44
  end
45
+ Statuses = {
46
+ :at_distribution_hub => [
47
+ /Ankunft im GLS Depot/,
48
+ /Ankunft im GLS Depot \(Ausland\)/,
49
+ /Ankunft im GLS Depot \(Großkunde\)/,
50
+ /Ankunft im GLS Depot \(HUB\)/,
51
+ /Ankunft im GLS Depot \(Regionalumschlag\)/,
52
+ /Ankunft im GLS Depot Business-Small Parcel Sortierung/,
53
+ /Ankunft im GLS Depot Grosskunden Checker/,
54
+ /Ankunft im GLS Depot Label nicht lesbar/,
55
+ /Ankunft im GLS Depot manuelle Sortierung/,
56
+ /Inbound to GLS location manually sorted/,
57
+ /Inbound to GLS location sorted as Business-Small Parcel/,
58
+ ],
59
+ :cancelled => [
60
+ /Daten aus dem GLS-System gelöscht/,
61
+ /Daten aus dem GLS-System gelöscht Betrifft Benachrichtigungskarte/,
62
+ ],
63
+ :delivery_failed => [
64
+ /Empfänger ist im Urlaub/,
65
+ /Nachnahme-Sendung nicht zugestellt/,
66
+ /Nicht zugestellt aufgrund falscher Adresse/,
67
+ /Nicht zugestellt da keine Annahme an diesem Tag/,
68
+ /Nicht zugestellt da Kunde nicht angetroffen/,
69
+ /Nicht zugestellt wegen Warenannahmeschluss/,
70
+ ],
71
+ :delivery_succeeded => [
72
+ /Zugestellt/,
73
+ ],
74
+ :destination_parcel_center => [
75
+ /Ankunft im GLS Depot \(Ziel\)/,
76
+ ],
77
+ :in_delivery_vehicle => [
78
+ /In Zustellung auf GLS-Fahrzeug/,
79
+ ],
80
+ :in_shipment => [
81
+ /Abfahrt vom GLS Depot/,
82
+ /Auslandspaket/,
83
+ /Paketabholung/,
84
+ /Pickup-Service Abholung erfolgt/,
85
+ /Pickup-Service Abholung erfolgt \(direkt\)/,
86
+ /Weitergeleitet Umverfuegt/,
87
+ ],
88
+ :notification_card => [
89
+ /Empfänger kontaktiert Betrifft Benachrichtigungskarte/,
90
+ ],
91
+ :on_hold => [
92
+ /Fehlleitung auf Zustellfahrzeug/,
93
+ /Fehlleitung im GLS-System/,
94
+ /Fehlsortierung/,
95
+ /Paket in Klärung/,
96
+ ],
97
+ :on_hold_at_depot => [
98
+ /Aufbewahrung im GLS Depot da keine Annahme an diesem Tag/,
99
+ /Aufbewahrung im GLS Depot da Kunde nicht angetroffen/,
100
+ /Eingelagert/,
101
+ /Eingelagert \(Platzmangel\)/,
102
+ /Lieferung unvollständig/,
103
+ /Nicht in Zustellung Neuer Zustelltermin vereinbart/,
104
+ ],
105
+ :return_shipment => [
106
+ /Annahme verweigert/,
107
+ /Annahme verweigert, weil Paket beschädigt/,
108
+ /Aufbewahrung im GLS Depot Lagerfrist im Paket Shop überschritten/,
109
+ /Nicht zugestellt Lagerfrist im Paket Shop überschritten/,
110
+ /Retoure: Paket ohne Inhalt/,
111
+ /Retoure: Paket schlecht verpackt/,
112
+ /Zum Versender retourniert/,
113
+ ],
114
+ :shipping_instructions_received => [
115
+ /Ankunft im GLS Depot der avisierten Sendungsdaten/,
116
+ /Daten übertragen, Paket noch nicht erhalten/
117
+ ],
118
+ :waiting_for_pickup_by_customer => [
119
+ /Nicht in Zustellung da Selbstabholung/,
120
+ /Zugestellt im GLS Paket Shop/,
121
+ ],
122
+ :new_delivery_attempt => [
123
+ /Keine Zustellung aus Zeitmangel/,
124
+ ],
125
+ }.freeze
60
126
  end
61
127
  end
@@ -0,0 +1,99 @@
1
+ # encoding: utf-8
2
+ module PolarExpress
3
+ module Hermes
4
+ class Tracker
5
+ attr_accessor :number
6
+ def initialize(number)
7
+ @number = number
8
+ end
9
+ def track!
10
+ info = ShippingInfo.new(@number)
11
+ info.statuses = timeline
12
+ info
13
+ end
14
+ private
15
+ def tracking_form_uri
16
+ html = Nokogiri::HTML open 'https://www.myhermes.de/wps/portal/paket/Home/privatkunden/sendungsverfolgung/'
17
+ URI('https://www.myhermes.de' + html.css('form#shipmentTracingDTO').first['action'])
18
+ end
19
+ def post_tracking_page
20
+ res = Net::HTTP.post_form(tracking_form_uri, 'shipmentID' => @number)
21
+ res.body
22
+ end
23
+ def page
24
+ @page ||= Nokogiri::HTML post_tracking_page
25
+ end
26
+ # TODO: implement cities
27
+ def timeline
28
+ page.css("table.table_shipmentDetails tbody tr").map do |tr|
29
+ tds = tr.css('td')
30
+ status_text = tds.last.text.strip
31
+ {
32
+ date: DateTime.parse(tds[0].text + " " + tds[1].text),
33
+ city: nil,
34
+ status: text_to_status(status_text),
35
+ text: status_text
36
+ }
37
+ end.reverse
38
+ end
39
+ def text_to_status(text)
40
+ match = Statuses.find do |status, regexes|
41
+ regexes.find { |regex| text =~ regex }
42
+ end
43
+ (match && match.first) || :other
44
+ end
45
+ end
46
+
47
+ Statuses = {
48
+ :at_distribution_hub => [
49
+ /Die Sendung wurde im Hermes (.+) sortiert/,
50
+ ],
51
+ :delivery_failed => [
52
+ /Bitte seien Sie uns bei der Adressklärung behilflich/,
53
+ /Der zu kassierende Betrag EUR (.+) lag beim Zustellversuch nicht vor/,
54
+ /Die angegebene Anschrift konnte nicht gefunden werden/,
55
+ /Die Annahme der Sendung wurde verweigert/,
56
+ ],
57
+ :delivery_succeeded => [
58
+ /Die Sendung wurde zugestellt/,
59
+ ],
60
+ :destination_parcel_center => [
61
+ /Die Sendung (wurde von|ist in) der Hermes Niederlassung (.+) (übernommen|eingetroffen)/,
62
+ ],
63
+ :in_delivery_vehicle => [
64
+ /Die Sendung befindet sich in der Zustellung/,
65
+ /Die Sendung ist auf Tour gegangen/,
66
+ ],
67
+ :in_shipment => [
68
+ /Die Sendung befindet sich auf dem Weg in die zuständige Hermes Niederlassung/,
69
+ /Die Sendung hat das Lager des Versenders verlassen/,
70
+ /Die Sendung wurde abgeholt/,
71
+ ],
72
+ :new_delivery_attempt => [
73
+ /Der Empfänger wurde nicht angetroffen/,
74
+ /Wir werden einen weiteren Zustellversuch durchführen/,
75
+ /Der Empfänger wurde zum (1|2). Mal nicht angetroffen. Wir werden einen weiteren Zustellversuch durchführen/,
76
+ ],
77
+ :on_hold_at_depot => [
78
+ /Die Sendung wird in der Hermes Niederlassung (.+) aufbewahrt/,
79
+ ],
80
+ :return_pickup_from_customer => [
81
+ /Der Abholauftrag ist in der Hermes Niederlassung eingegangen/,
82
+ /Der Abholauftrag wurde an die Hermes Logistik Gruppe übermittelt/,
83
+ /Die Abholung wird durchgeführt/,
84
+ ],
85
+ :return_shipment => [
86
+ /Die Sendung wird an den Versender zurückgeführt/,
87
+ /Die Sendung wurde im Hermes PaketShop abgeholt und für den weiteren Transport sortiert/,
88
+ ],
89
+ :shipping_instructions_received => [
90
+ /Die Sendungsdaten wurden an die Hermes Logistik Gruppe übermittelt/,
91
+ ],
92
+ :waiting_for_pickup_by_customer => [
93
+ /Der Empfänger wurde zum 3. Mal nicht angetroffen/,
94
+ /Die Sendung ist im Hermes PaketShop eingegangen/,
95
+ /Die Sendung liegt im Hermes PaketShop zur Abholung bereit/,
96
+ ]
97
+ }.freeze
98
+ end
99
+ end
@@ -4,4 +4,17 @@ module PolarExpress
4
4
  def self.new(*args)
5
5
  Tracker.new(*args)
6
6
  end
7
+
8
+ # returns a courier's symbol given it's name.
9
+ # ideally it should be much smarter, supporting company's name, etc.
10
+ def self.identify_courier(name)
11
+ case name.upcase
12
+ when /DHL/
13
+ :DHL
14
+ when /GLS/
15
+ :GLS
16
+ when /HERMES/
17
+ :Hermes
18
+ end
19
+ end
7
20
  end
@@ -2,24 +2,12 @@ module PolarExpress
2
2
  class Tracker
3
3
  attr_reader :courier, :shipping_number
4
4
  def initialize(courier, shipping_number)
5
- @courier = identify_courier(courier)
5
+ @courier = PolarExpress::identify_courier(courier)
6
6
  @shipping_number = shipping_number
7
7
  @courier_tracker = Object.const_get("PolarExpress::#{@courier}::Tracker").new(@shipping_number)
8
8
  end
9
9
  def track!
10
10
  @track ||= @courier_tracker.track!
11
11
  end
12
-
13
- private
14
- # returns a courier's symbol given it's name.
15
- # ideally it should be much smarter, supporting company's name, etc.
16
- def identify_courier(name)
17
- case name.upcase
18
- when 'DHL'
19
- :DHL
20
- when 'GLS'
21
- :GLS
22
- end
23
- end
24
12
  end
25
13
  end
@@ -1,3 +1,3 @@
1
1
  module PolarExpress
2
- VERSION = "0.0.3.alpha"
2
+ VERSION = "0.0.4.alpha"
3
3
  end
@@ -0,0 +1,13 @@
1
+ require 'net/https'
2
+
3
+ # gotten from: https://github.com/stevegraham/certified/blob/master/certified.rb
4
+
5
+ Net::HTTP.class_eval do
6
+ alias _use_ssl= use_ssl=
7
+
8
+ def use_ssl= boolean
9
+ self.ca_file = "#{File.dirname(__FILE__)}/certs/cacert.pem"
10
+ self.verify_mode = OpenSSL::SSL::VERIFY_PEER
11
+ self._use_ssl = boolean
12
+ end
13
+ end