polar_express 0.0.3.alpha → 0.0.4.alpha

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