Herausfinden zu welchem Versanddienstleister eine Tracking ID gehört

Posted on 29. Juli 2016 Comments

Wir hatten erst letztens das Problem, dass wir automatisiert Tracking IDs zugespielt bekommen, die von mehreren Versanddienstleistern stammen könnten. Dies ist z. B. beim ERP Plentymarkets der Fall, wenn man Pakete per FBA verschickt. Allerdings nur, wenn die Aufträge nicht von Amazon kommen, sondern ein sog. Multichannel Auftrag vorliegt, also Amazon nur als Logistiker benutzt wird. In der API Rückmeldung steht zwar m.E. der Versanddienstleister, allerdings implementiert Plentymarkets kein Mapping.

Um den richtigen Provider zu finden, ohne jedes mal alle Websites abzuklappern habe ich das PHP Paket shipping-service-providers-check geschrieben und über composer verfügbar gemacht. Es macht automatisiert genau das. Falls es nicht geht, weil z.B. die Flash oder JavaScript im Spiel sind oder es gar keine öffentliche Seite gibt (->Amazon Logistics), kann die Nummer auch anhand des Formats geprüft werden, also mit einem regulärem Ausdruck.

Theoretisch ist es möglich, dass die Tracking ID gleichzeitig bei mehreren Providern für unterschiedliche Sendungen gültig ist. Das ist zugegebenermaßen extrem unwahrscheinlich. Dennoch gibt mein Paket ein Array von Paketdienstleistern, jeweils mit einem Boolean (true/false), zurück.

Code Erklärung

Die Abhängigkeiten sind fabpot/goutte (ein Website scraper) und danielstjules/stringy (für Stringvergleiche). Da ich von PHPs use function Gebrauch mache, ist PHP Version 5.6 notwendig.

Die Klasse Check enthält neben dem Konstruktur, der die TrackingID erwartet, 3 weitere Methoden.

  • getProviders() – gibt alle Provider zurück
  • checkAll($extraProviders) – gibt das eben erwähnte Array zurück. Das hier ist die eigentlich wichtige öffentliche Methode
  • check() – private, wird in einer Schleife von checkAll() aufgerufen

In der Datei default_providers.php sind in einem Array alle implementierten Versanddienstleister aufgeführt. Hier können auch weitere hinzugefügt werden, bzw. in diesem Format an checkAll() übergeben werden. Jeder Dienstleister hat 3 Parameter:

  • base_url – URL, die goutte zusammen mit der tracking ID aufrufen wird
  • filter – HTML Tag auf der Seite, nach dem gesucht bzw. das durchsucht werden soll
  • search_string  – nach diesem String wird in dem HTML Tag gesucht

Der eigentlich wichtige Teil ist deswegen die Methode checkOnline()

$crawler = $this->client->request('GET', $parameters["base_url"] . $this->trackingId);

in dieser Zeile ruft goutte die vorher definierte base_url mit der Tracking ID auf. Die URL muss deshalb im Format http://example.com?tracking_id= vorliegen. Aus der Tracking ID 123456 lautet dann der Aufruf http://example.com?tracking_id=123456.

$crawler->filter($parameters["filter"])->each(function ($node) use ($parameters) {
            if (s($node->text())->contains($parameters["search_string"])) {
                return true;
            }
            return false;

In dem HTML Tag aus dem filter Parameter wird jetzt nach dem String search_string mittels stringys contains() gesucht. Sollte das der Fall sein, wird true zurückgegeben. Da die Funktion each ein Array zurückgibt und es möglich ist, dass das relevante HTML Tag mehrmals vorkommt, wird danach geguckt ob true in diesem array überhaupt vorkommt, auch wenn andere Einträge in diesem Array eben false sind.

return in_array(true, ...)

Dies wird in einer Schleife in checkAll() durchgegangen:

        foreach ($shippingProviders as $shippingProvider => $parameters) {
            $response[$shippingProvider] = $this->check($parameters);
        }

Außerdem ist es durch folgende Zeile möglich einen Versanddienstleister hinzuzufügen oder zu ersetzen (beides passiert durch array_merge())

  if (isset($shippingProviders)) {
            $shippingProviders = array_merge($defaultShippingProviders, $shippingProviders);
        } else {
            $shippingProviders = $defaultShippingProviders;
        }

Die Methode zum Überprüfen des regulärem Ausdrucks checkFormat() ist denkbar simpel:

  boolval(preg_match($parameters["regex"], $this->trackingId))

Da preg_match 1 zurückgibt, falls der Regex zutrifft, und 0 falls nicht (false wenn ein Fehler aufgetreten ist), muss um das Ergebnis noch boolval().

Die Anleitung zum Benutzen des Pakets ist auf GitHub bzw. Packagist. Zur Zeit des Verfassens dieses Artikels sind die folgenden Versanddienstleister implementiert:

  • DHL
  • GLS
  • UPS
  • Hermes
  • Amazon Logistics

Fedex, DPD und TNT gestalten sich schwierig, da diese Informationen per JavaScript nachladen und goutte das nicht beherrscht. Ggf. werde ich nochmal ein npm Paket mit zombie.js und/oder phantomjs schreiben.

#Microadventure by bike in Brandenburg, Germany

Posted on 30. Juni 2016 Comments

Brandenburg is German state in the east at the Polish border. Just as it’s neigbouring state Mecklenburg-Vorpommern, it doesn’t have a lot of inhabitants compared to the size. But a lot of lakes and one bis plus: in about the middle lays Berlin and their border is easily reachable by the public transportation („S-Bahn“) of Berlin. So 3 weeks ago on a Friday after work I packed my panniers, booked a Flixbus ticket for me and my bike and drove to Berlin to visit my friend Martin. We then started (reasonably) early Saturday morning in Oranienburg, north or Berlin, and drove until the early evening: through forests and fields, on normal paved roads but also gravel. With lots of lakes  and nobody around we swam in every other lake at 30°C+ and sunshine. We then camped at a lake and ate our instant noodles. Next day we started with some nice pancakes and a bit later, a small lunch at a restaurant and made it just in time to a café, before a thunderstorm broke loose. After that we sprinted 30km back to the train station. In total we drove about 170km. I took the bus back to Hamburg that very night and went to work on Monday again. Awesome weekend, we will definitely do this again. Thanks for the idea, Alastair.

2016-06-05 10.06.52 2016-06-05 15.04.58 2016-06-05 09.40.46 2016-06-04 18.54.39 2016-06-04 18.54.30 NOVATEK CAMERA

brandenburg

MySQL Workbench Error: line contains NULL byte

Posted on 21. April 2016 Comments

When importing CSV (or other) files into the database, scripts (especially PHP or C-related languages), will stop if there is – for whatever reason – a NULL byte in your file because it signals end of file/string, see Null bytes related issues.

So when importing a file like that with MySQL Workbench you will get this error:

line contains NULL byte

You can solve this by using the commandline tool tr (from coreutils):

tr < file-with-nulls -d '\000' > file-without-nulls

To check if there are any null bytes in your file, use the python IDE and type in:

open('filename.ext').read().index('\0')

Thanks to Pointy from Stackoverflow

ThinkPad Compact Bluetooth Keyboard with TrackPoint Einfügen beim Scrollen Fix Ubuntu 14.04

Posted on 16. Dezember 2015 Comments

Die ThinkPad Bluetooth Tastatur von Lenovo verursacht unter Ubuntu 14.04 einen Fehler, so dass beim Scrolling mit TrackPoint und mittlerer Maustaste gleichzeitig die Einfügen Operation ausgelöst wird. Ich will aber auf keinen Fall weder auf die Paste Funktion der mittleren Maustaste noch auf das Scrolling verzichte und habe lange gesucht bis ich die folgende Lösung gefunden habe: tp-compact-keyboard-backport.

  1. git clone https://github.com/mithro/tp-compact-keyboard-backport.git
  2. make
  3. make install
  4. hid-lenovo.ko in /lib/modules/3.19-*version*-generic/kernel/drivers/hid/ austauschen.
  5. (leider muss man das auch nach jedem Kernel Update machen, da sonst die mittlere Taste gar nicht mehr funktioniert)
  6. Neustart

Außerdem war noch tp-compact-keyboard hilfreich (daher habe ich auch das -backport repository). Ggf. ist dafür ein Kernel Update nötig bzw. einfacher als ein Kernel-Patch zu installieren.

Simple wget crawler for list of files

Posted on 6. August 2015 Comments

This script could be helpful to download a set of files from a webserver, that you don’t have (S)FTP access to. The input file consists of a list of filenames, one name each line.


#!/bin/bash
file=$1
WEBSERVER="http://webserver.tld/folder/"
while IFS= read -r line; do
FULLURL="$WEBSERVER$line"
wget -nc -R --spider $FULLURL
done < "$file"

At first, the first command line argument is saved into the variable file. Then the Webserver address is saved to the WEBSERVER variable. IFS stands for Internal Field Separator. It’s used to read line by line through the file in the while loop that ends in the last line. Inside of the loop, the read line is concatenated with the webserver address into FULLURL. Then, wget is used with the parameters -nc for checking if the file is not already present in the current folder, -R for downloading and –spider for checking the existence on the webserver.

You can find the script on GitHub.

Collect currency exchange rates in a MySQL database with PHP and fixer.io API

Posted on 4. August 2015 Comments

If you work in a company that buys and sells goods in many different currencies, it might be a good idea, to use the latest exchange rates. Also, it might be useful, to store old exchange rates to clarify/verify old business decisions. If once a day is enough for you, fixer.io offers a free simple Rest API. A lot of the code at my work is written in PHP but I usually use the request library in JavaScript and Python, so I’m using it in this example too. A common PHP solution would be guzzle. But first, get composer (the PHP counterpart to npm or pip):

$ curl -sS https://getcomposer.org/installer | php

$ php composer.phar require rmccue/requests

The mysql_ commands are deprecated (and removed in PHP 7), use mysqli or PDO. Also you should use some sort of framework for the database access, like medoo or a proper ORM. This is just proof of concept.


$base = 'EUR';
$request = Requests::get('https://api.fixer.io/latest?base=' . $base, array('Accept' => 'application/json'));
if ($request->status_code == 200) {
$response = json_decode($request->body);
$GBP = $response->rates->GBP;
$CAD = $response->rates->CAD;
$USD = $response->rates->USD;
$NOK = $response->rates->NOK;
$CNY = $response->rates->CNY;
$rBase = mysql_real_escape_string($response->base);
$date = mysql_real_escape_string($response->date);
$currencies = mysql_real_escape_string("1.0, $USD, $GBP, $NOK, $CNY, $CAD");
$qry = "INSERT INTO `exchange_rates_fixerio`(date, base, eur, usd, gbp, nok, cny, cad) VALUES ('$date', '$rBase', $currencies);";
$insert = mysql_query($qry, $mysqlConnection) or print mysql_error();
}

I assume the database connection is defined earlier, there’s lot’s of documentation for that. Because we are from Europe, I chose Euro (EUR) as the base currency. Apart from the get() method, you need nothing else, to send a request. If the request returns an OK(200), the response is read and saved into different variables, e.g. for British Pounds, US Dollar, Canadian Dollar, Chinese Renminbi and Norwegian Krone. Just to make sure we have the right base, it’s also parsed. From there it’s only a simple INSERT INTO (as said before, use a framework for that)

The table could look like this:

CREATE TABLE `echange_rates_fixerio` (
`date` date NOT NULL,
`base` varchar(3) NOT NULL,
`eur` double NOT NULL,
`usd` double NOT NULL,
`gbp` double NOT NULL,
`nok` double NOT NULL,
`cny` double NOT NULL,
`cad` double NOT NULL
)

 

You can also find this code on GitHub.

Using nanoc for podcast feeds

Posted on 23. Juli 2015 Comments

Nanoc is a static site generator much like jekyll or octopress, but with a more minimalistic approach. These generators are not necessarily the most suitable choice for a podcast website, but it’s possible and you might save up on webspace and traffic when you use GitHub or Neocities.

Creating the podcast feed is basically like writing a normal Atom feed for the blog, since podcast feeds ARE indeed feeds with an enclosure tag in which the URL to the audio or video file is placed. This guide does not include the itunes tags. I might add it one day. Follow the instructions and use the documentation for the Helper Blogging. Tag your podcast episodes as kind:article.

The media files are placed in content/mp3 and content/opus, which is where the links in the feeds will point to later.

I invented the fields mp3 and opus, since these are the file formats I want to use. The values are the filenames. The header of a new episode/post would look like

---
title: 001 - Podcast Episode Title
created_at: 2015-03-14 09:00:00 +0000
kind: article
tags: [podcast,topic]
mp3: 001-podcast-episode-title.mp3
opus: 001-podcast-episode-title.opus
---

This has to be filled manually everytime, so make sure you have the exact filename, as some podcast clients won’t allow correction of the URL.

The next step is to write different feeds for the formats. For that, I’m using the new field format, which will be interpreted by the Helper class later on. For example, I called my normal feed blogfeed and the podcast feeds mp3feed and opusfeed. Create the file blogfeed.erb in the content folder and fill it with the following:

<%= atom_feed :title => 'repats podcast blog', :author_name => 'repat',
:author_uri => 'http://repat.de', :limit => 10, :format => 'blog' %>

The mp3feed.erb and opusfeed.erb are filled accordingly:

<%= atom_feed :title => 'repats podcast mp3', :author_name => 'repat',
:author_uri => 'http://repat.de', :limit => 10, :format => 'mp3' %>

<%= atom_feed :title => 'repats podcast opus', :author_name => 'repat',
:author_uri => 'http://repat.de', :limit => 10, :format => 'opus' %>

The next step is to use the Blogging locally in your nanoc installation. To do that you need to copy it from the gems folder into your lib folder. For me, that was

$ cp /var/lib/gems/1.9.1/gems/nanoc-3.7.5/lib/nanoc/helpers/blogging.rb lib/

It should be included like this in the lib/default.rb

include Nanoc3::Helpers::Blogging

Add the following attribute to the AtomFeedBuilder class

attr_accessor :format

If you don’t trust yourself to always remember the files you might want to  add this exception to the validate_feed_item function

if format.nil?
raise Nanoc::Errors::GenericTrivial.new('Cannot build Atom feed: no format(mp3,opus,blog) in params, item or site config')
end

After the # Add link comment is a good place to insert the  enclosure tag. File.size() will only work if the files are there and the exact same name. This code could probably be written a bit more safely, but I’m not a ruby developer and since I will have an mp3 file and and opus file in every post it’s not a problem this way.

# Add podcast enclosure
if format == 'mp3'
xml.link(href:"http://yourpodcast.com/mp3/" + a[:mp3],length:File.size("content/mp3/" + a[:mp3]), type:"audio/mpeg", rel:"enclosure")
elsif format == 'opus'
xml.link(href:"http://yourpodcast.com/opus/" + a[:opus],length:File.size("content/opus/" + a[:opus]), type:"audio/mpeg", rel:"enclosure")
end

To interpret the mp3 and opus attribute from earlier in the actual post, the last step is to add this line to the atom_feed function:

      builder.format            = params[:format]

You might need to install builder to let this run

$ sudo gem install builder

The only thing left to do is to edit the Rules file:

compile '/blogfeed' do
filter :erb
end
compile '/mp3feed' do
filter :erb
end
compile '/opusfeed' do
filter :erb
end
[...]
route '/blogfeed' do
'/blogfeed.xml'
end
route '/mp3feed' do
'/mp3feed.xml'
end
route '/opusfeed' do
'/opusfeed.xml'
end

 

You can find the blogging.rb and the Rules file on GitHub.