Notdienst Apotheke via jsonmod

Begonnen von binford6000, 27 März 2023, 14:10:27

Vorheriges Thema - Nächstes Thema

herrmannj

Achtung, das ist ganz, ganz dünnes Eis.

betateilchen

Zitat von: binford6000 am 02 Mai 2023, 11:37:24Ich habe mal eine nette Mail an aponet-download_at_avoxa.de geschrieben und unser Token-Dilemma beschrieben.

Wenn in der Anfrage ein Bezug zu FHEM enthalten ist, wäre das m.E. eher eine Aufgabe für die Verein-Verantwortlichen gewesen.
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

herrmannj

das bringt mich in die Situation den Vereins Hut aufsetzen zu müssen und zusätzlich als Autor von JsonMod Stellung zu nehmen:

Der Verein betreibt keine individuellen fhem Installation, sondern stellt (unter anderem) die Infrastruktur zur Pflege des codes eines community basierten open-source Projekt zur Verfügung. Der Betrieb von fhem erfolgt in der Verantwortung des jeweiligen Anwenders.

Aus der von euch verlinkten Seite geht unmissverständlich hervor dass die Daten nicht zum freien Gebrauch bereitgestellt werden:
ZitatDie Avoxa Mediengruppe wurde von der Bundesapothekerkammer (BAK) beauftragt, die offiziellen Apotheken-Notdienstdaten für Deutschland zu vermarkten.

Das Token stellt damit eine "Wirksame technische Maßnahmen zum Schutz eines nach diesem Gesetz geschützten Werkes" (95a UrhG) dar. Daneben bewegt ihr euch in einer Handvoll weiterer Gesetze vom ZKDSG bis zum § 202c StGB.

Mit Vereinshut und als Autor: JsonMod ist ein universelles Tool zum Auslesen und Verarbeiten von strukturierten Daten im Json Format.

In allen Fällen liegt die Verantwortung für die Rechtmäßigkeit des individuellen Verarbeitung ausschließlich beim jeweiligen user. Dazu gehört, dass die Erlaubnis des Datenanbieters explizit oder implizit erteilt wurde.

JoWiemann

Zitat von: herrmannj am 02 Mai 2023, 14:03:15Aus der von euch verlinkten Seite geht unmissverständlich hervor dass die Daten nicht zum freien Gebrauch bereitgestellt werden:
ZitatDie Avoxa Mediengruppe wurde von der Bundesapothekerkammer (BAK) beauftragt, die offiziellen Apotheken-Notdienstdaten für Deutschland zu vermarkten.

Nun ja,

das Zitat ist aus dem Zusammenhang gerissen und für private Nutzung so nicht definiert. Bitte hier immer die gesamte Seite lesen, welche mit folgender Nutzungsbeschreibung startet:
ZitatSie möchten die aktuellen Apotheken-Notdienste und Apotheken-Nachtdienste in Ihre Online-Angebote einbinden?
Sie suchen nach einer automatisierten Notdienstanzeige, um Ihrer Informationspflicht nach § 23 ApBetrO (Dienstbereitschaft) nachzukommen?
Dann sind Sie bei uns richtig!

Grüße Jörg
Jörg Wiemann

Slave: RPi B+ mit 512 MB, COC (868 MHz), CUL V3 (433.92MHz SlowRF); FHEMduino, Aktuelles FHEM

Master: CubieTruck; Debian; Aktuelles FHEM

herrmannj

bei aller Sympathie und Gewogenheit, nur weil auf der "Nimm 2" Packung draufsteht "Nimm 2" bedeutet das nicht, dass Du im Discounter Deiner Wahl 2 Stück einstecken sollst ;)

Christoph Morrison

Zitat von: herrmannj am 02 Mai 2023, 14:56:59bei aller Sympathie und Gewogenheit, nur weil auf der "Nimm 2" Packung draufsteht "Nimm 2" bedeutet das nicht, dass Du im Discounter Deiner Wahl 2 Stück einstecken sollst ;)

Wenn er sie bezahlt? ;) Steht ja nicht drauf "Nimm 2 und bezahle 1". Warten wir doch ab, was der Betreiber sagt.

binford6000

ZitatWarten wir doch ab, was der Betreiber sagt.

Nach einer Woche hat sich noch keiner gemeldet - was zu erwarten war...
Beim Verein hat sich vermutlich auch noch keiner gemeldet - zumindest ist niemand auf mich zu gekommen.


SamNitro

Besteht denn eine Möglichkeit den Token mit FHEM auszulesen? Oder eine Möglichkeit per httpmod an die Daten zu kommen?
(Intel-Nuc Proxmox) (Homematic) (EnOcean) (CUL868) (CUL433) (Zigbee2MQTT) (ESP8266) (Echo) (DUOFERN)

Christoph Morrison

Zitat von: SamNitro am 09 Mai 2023, 15:26:00Besteht denn eine Möglichkeit den Token mit FHEM auszulesen? Oder eine Möglichkeit per httpmod an die Daten zu kommen?

Da wird ein Secret erzeugt, vermutlich auf Session-Basis. D.h. man müsste quasi einen Client simulieren, der dieses Secret bekommen kann. Das Secret wird im Formular aber nicht verwendet, lässt sich also nicht preemptiv auslesen. Für HTTPMOD / JSONMOD ist der Schuhe denke ich eine Nummer zu groß. Hier müsste man vermutlich ein eigenes Modul entwickeln.

Wolle02

Wäre es eventuell eine Idee auf eine Alternative umzusteigen?

https://www.apotheken-umschau.de/apotheken-notdienst/

Kann sich das mal einer anschauen, der sich aufs Parsen mit HTTPMOD versteht?

yersinia

Christoph Morrison hat höchstwahrscheinlich schon recht, dass es mit derzeitigen Standard-Bordmitteln einfach nicht lösbar zu sein scheint.

Zitat von: Wolle02 am 10 Mai 2023, 18:16:04Wäre es eventuell eine Idee auf eine Alternative umzusteigen?
Man kann es versuchen, ist leider nicht so ganz schön wie bei aponet direkt.

Anbei ein erster Vorschlag als Basis. Sicher nicht perfekt und durchaus ausbaufähig. PLZ (replacement01Value), Ort (replacement02Value), Straße (replacement03Value) und Datum ((replacement04Value)) können gesetzt werden.
defmod NotApo HTTPMOD https://www.apotheken-umschau.de/apotheken-notdienst/ 86400
attr NotApo alignTime 09:02
attr NotApo reading01Name NotApo_Name
attr NotApo reading01Regex <p>[\s]*<strong>(.+)[(]ca
attr NotApo reading02Name NotApo_Street
attr NotApo reading02Regex <br>[\s]*(.+)<br>
attr NotApo reading03Name NotApo_City
attr NotApo reading03Regex <br>[\s]*(.*)<br>[\s]*<\/p>
attr NotApo reading04Name NotApo_TimeText
attr NotApo reading04Regex <p class="font-size-xs">(.*)<\/p>
attr NotApo reading05Name NotApo_Tel
attr NotApo reading05Regex dashed">[\s]+<p>Telefon:[\W]+(.*)<\/p>
attr NotApo replacement01Mode text
attr NotApo replacement01Regex %%ZIP%%
attr NotApo replacement01Value 12345
attr NotApo replacement02Mode text
attr NotApo replacement02Regex %%CITY%%
attr NotApo replacement02Value #
attr NotApo replacement03Mode text
attr NotApo replacement03Regex %%STREET%%
attr NotApo replacement03Value #
attr NotApo replacement04Mode expression
attr NotApo replacement04Regex %%DATE%%
attr NotApo replacement04Value return DateTime->now(time_zone => 'local')\
          ->set_time_zone('floating')\
        ->add(days => 0)\
        ->strftime('%Y-%m-%d');;
attr NotApo requestData01 emergency_pharmacy_search%5Bplz%5D=%%ZIP%%&emergency_pharmacy_search%5Bort%5D=%%CITY%%&emergency_pharmacy_search%5Bstrasse%5D=%%STREET%%&emergency_pharmacy_search%5Bdatum%5D=%%DATE%%
attr NotApo requestHeader01 Accept: text/html,application/xhtml+xml,application/xml;;q=0.9,image/avif,image/webp,*/*;;q=0.8
attr NotApo requestHeader02 Accept-Language: de,en-US;;q=0.7,en;;q=0.3
attr NotApo requestHeader03 Content-Type: application/x-www-form-urlencoded
attr NotApo requestHeader04 Accept-Encoding: gzip, deflate, br
attr NotApo requestHeader05 Host: www.apotheken-umschau.de
attr NotApo requestHeader06 User-Agent: Mozilla/5.0 (X11;; Linux x86_64;; rv:109.0) Gecko/20100101 Firefox/113.0
attr NotApo room YourRoom
attr NotApo stateFormat { my $ret = "<div style=\"text-align: left;;\">";;\
    $ret .= "<font style=\"font-weight: bold;;\">".ReadingsVal($name,"NotApo_Name","?")."</font><br />";;\
        my $osmlink = "https://www.openstreetmap.org/search?query=";;\
            $osmlink .= ReadingsVal($name,"NotApo_Street","")."%2C ";;\
            $osmlink .= ReadingsVal($name,"NotApo_City","");;\
    $ret .= "<a href=\"".$osmlink."\" rel=\"noopener noreferrer\" target=\"_blank\">";;\
    $ret .= ReadingsVal($name,"NotApo_Street","?")."<br />";;\
    $ret .= ReadingsVal($name,"NotApo_City","?")."</a><br />";;\
    $ret .= "<br />";;\
        my $numberlink = ReadingsVal($name,"NotApo_Tel","");;\
            $numberlink =~ s/\s+//g;;\
            $numberlink =~ s/\///g;;\
    $ret .= "Tel.: <a href=\"tel:+49".substr($numberlink, 1)."\">".ReadingsVal($name,"NotApo_Tel","")."</a><br />";;\
    $ret .= "<br />";;\
    $ret .= ReadingsVal($name,"NotApo_TimeText","?")."<br />";;\
    $ret .= "</div>";;\
    return $ret;;\
}
attr NotApo webCmd reread
Datum kann auch leer bleiben, dann nimmt die RentnerBravo aka Apotheken Umschau anscheinend das Tagesdatum. Für Tage in der Zukunft einfach das Offset (->add(days => 0)) erhöhen.
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl

KyleK

#26
Ich habe das Problem mit dem Token folgendermaßen gelöst:

Der Token steht in einer Javascript-Datei, pharmacymap-*.js, welche beim Aufruf der Haupt-URL  https://www.aponet.de/apotheke/notdienstsuche geladen wird.

Mit einem 2-stufigen HTTPMOD bestimme ich zunächst den Pfad zur pharmacymap-*.js, und danach hole ich den Token aus dieser Datei:
defmod apotheke_token HTTPMOD none 600
attr apotheke_token event-on-change-reading .*
attr apotheke_token get1FollowGet token
attr apotheke_token get1Name pharmacymap
attr apotheke_token get1Poll 1
attr apotheke_token get1Regex src="(\/typo3temp\/assets\/compressed\/pharmacymap-\w+?\.js)
attr apotheke_token get1URL https://www.aponet.de/apotheke/notdienstsuche
attr apotheke_token get2Name token
attr apotheke_token get2OExpr $val=urlEncode($val)
attr apotheke_token get2Poll 1
attr apotheke_token get2PollDelay 5
attr apotheke_token get2Regex randomToken\s*=\s*'(\w+)'
attr apotheke_token get2URL https://www.aponet.de%%value%%
attr apotheke_token replacement2Mode reading
attr apotheke_token replacement2Regex %%value%%
attr apotheke_token replacement2Value pharmacymap

In einem JsonMOD Device wird die nächste Notfallapotheke abgefragt:

defmod apotheke JsonMod https://www.aponet.de/apotheke/notdienstsuche?tx_aponetpharmacy_search[action]=result&tx_aponetpharmacy_search[controller]=Search&&tx_aponetpharmacy_search[search][lat]=xx.xxxxxxx&tx_aponetpharmacy_search[search][lng]=xx.xxxxxxx&tx_aponetpharmacy_search[search][radius]=8&tx_aponetpharmacy_search[token]=[my_token]&type=1981
attr apotheke alias Notdienstapotheke
attr apotheke group Notfall
attr apotheke icon fa_ambulance
attr apotheke interval 30 9 * * *
attr apotheke readingList single(jsonPath('$.results.apotheken.apotheke[0].name'), 'name', 'none');;\
single(jsonPath('$.results.apotheken.apotheke[0].strasse'), 'strasse', 'none');;\
single(jsonPath('$.results.apotheken.apotheke[0].ort'), 'ort', 'none');;\
single(jsonPath('$.results.apotheken.apotheke[0].plz'), 'plz', 'none');;\
single(jsonPath('$.results.apotheken.apotheke[0].distanz'), 'distanz', 'none');;\
single(jsonPath('$.results.apotheken.apotheke[0].telefon'), 'telefon', 'none');;\
multi(jsonPath('$.results.apotheken.apotheke.[0]'), "map_position", concat("https:\/\/www.openstreetmap.org\/?mlat=", property('.latitude'), "&mlon=", property('.longitude'), "#map=18\/", property('.latitude'), "\/", property('.longitude')));;\
multi(jsonPath('$.results.apotheken.apotheke.[0]'), 'map_route', concat("https:\/\/www.openstreetmap.org\/directions?engine=", AttrVal($name,"osm_engine","fossgis_osrm_car"), "&route=", AttrVal("global","latitude","0"), "%2C", AttrVal("global","longitude","0"), "%3B" , property('.latitude'), "%2C", property('.longitude')));;
attr apotheke room Dashboard
attr apotheke stateFormat [$name:name], [$name:telefon], [$name:strasse], [$name:plz] [$name:ort] ([$name:distanz:r1] km)

Weil ich hier in dem Device ein "secret" 'my_token' verwende, benötige ich noch ein DOIF, was das Secret (den Token) setzt, wenn er sich geändert hat:

defmod di.apotheke DOIF ([apotheke_token:token]) (set apotheke secret my_token [apotheke_token:token])
attr di.apotheke do always

Ich hoffe jemand kann damit was anfangen :)

FHEM on Raspberry Pi 3B+
CUL868
7x MAX! Thermostat, 8x MAX! Fensterkontakte
Conbee II + deConz, TradFri Lampen, Osram Smart+ Steckdosen

betateilchen

#27
Danke für die Recherche und die Umsetzung!

Würde es nicht reichen, das Token einfach um 09:29 Uhr abzuholen, anstatt alle 10 Minuten zu prüfen, ob es sich geändert hat?
Dann würde man die vielen Abfragen von der gleichen IP Adresse aus vermeiden.

defmod apotheke_token HTTPMOD none 0
... (Rest wie bisher)

defmod apotheke_reload at *09:29:00 set apotheke_token reread;; sleep 5;; set apotheke secret my_token [apotheke_token:token]
-----------------------
Formuliere die Aufgabe möglichst einfach und
setze die Lösung richtig um - dann wird es auch funktionieren.
-----------------------
Lesen gefährdet die Unwissenheit!

KyleK

Die 10 Minuten Interval sind nur drin, weil ich wissen wollte/will, wann sich der Token ändert :)

Du hast natürlich recht, der muss nicht periodisch geholt werden, sondern kann nur einmal kurz vorher abgefragt werden.
FHEM on Raspberry Pi 3B+
CUL868
7x MAX! Thermostat, 8x MAX! Fensterkontakte
Conbee II + deConz, TradFri Lampen, Osram Smart+ Steckdosen

yersinia

#29
Danke KyleK, das funktioniert ausgezeichnet. :)

Passend zu deiner JM Device Definiton noch eine Änderung/Erweiterung:
- alle Apotheken in der Nähe als Readings
- userattr für die OSM Engine
- die FHEM Koordinaten (longitude/latitude aus dem global Device) für die Routenfindung
- Anzeige der obersten drei Apotheken (via stateFormat)
attr apotheke userattr osm_engine:graphhopper_car,fossgis_osrm_car,fossgis_valhalla_car,graphhopper_bicycle,fossgis_osrm_bike,fossgis_valhalla_bicycle,graphhopper_foot,fossgis_osrm_foot,fossgis_valhalla_foot
attr apotheke osm_engine fossgis_osrm_car
attr apotheke readingList multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_name"), property('.name'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_street"), property('.strasse'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_zip"), property('.plz'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_city"), property('.ort'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_tel"), property('.telefon'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_fax"), property('.fax'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_email"), property('.email'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_distance"), property('.distanz'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_longitude"), property('.longitude'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_latitude"), property('.latitude'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_start_date"), property('.startdatum'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_start_time"), property('.startzeit'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_end_date"), property('.enddatum'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_end_time"), property('.endzeit'));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_map_position"), concat("https:\/\/www.openstreetmap.org\/?mlat=", property('.latitude'), "&mlon=", property('.longitude'), "#map=18\/", property('.latitude'), "\/", property('.longitude')));;\
multi(jsonPath("\$.results.apotheken.apotheke.[*]"), concat(sprintf('%02d', count()),"_map_route"), concat("https:\/\/www.openstreetmap.org\/directions?engine=", AttrVal($name,"osm_engine","fossgis_osrm_car"), "&route=", AttrVal("global","latitude","0"), "%2C", AttrVal("global","longitude","0"), "%3B" , property('.latitude'), "%2C", property('.longitude')));;
attr apotheke stateFormat {    my $ret .= "<div style=\"display:table;;width:100%;;\">";; #table\
    for(my $i = 0;;$i <= 2;;$i++) {\
        my $id = sprintf('%02d', $i);;\
        $ret .= "<div style=\"display:table-row;;width:100%;;\">";; #row\
            $ret .= "<div style=\"display:table-cell;;padding:3pt;;width:50%;;text-align:left;;\">";; #cell\
                $ret .= "<a href=\"".ReadingsVal($name,$id."_map_position","https:\/\/www.osm.org");;\
                $ret .= "\" rel=\"noopener noreferrer\" target=\"_blank\" style=\"font-weight:bold;;\">".ReadingsVal($name,$id."_name","?")."</a>";;\
                $ret .= " (".sprintf("%.1f", ReadingsNum($name,$id."_distance",0))."km)<br \/>";;\
                $ret .= "<a href=\"".ReadingsVal($name,$id."_map_route","https:\/\/www.osm.org")."\" rel=\"noopener noreferrer\" target=\"_blank\">";;\
                $ret .= ReadingsVal($name,$id."_street","")."<br /\>";;\
                $ret .= ReadingsVal($name,$id."_zip","")."&nbsp;;".ReadingsVal($name,$id."_city","");;\
                $ret .= "</a>";;\
            $ret .= "</div>";; #/cell\
            $ret .= "<div style=\"display:table-cell;;width:50%;;text-align:left;;\">";; #cell\
                my $numberlink = ReadingsVal($name,$id."_tel","");;\
                $numberlink =~ s/\s+//g;;\
                $numberlink =~ s/\///g;;\
                $ret .= "Tel.: <a href=\"tel:+49".substr($numberlink, 1)."\">".ReadingsVal($name,$id."_tel","")."</a><br />";;\
if(ref(ReadingsVal($name,$id."_fax","")) ne 'ARRAY') {\
$ret .= "Fax: ".ReadingsVal($name,$id."_fax","");;\
}\
$ret .= "<br />";;\
                $ret .= "eMail: <a href=\"mailto:".ReadingsVal($name,$id."_email","")."\">".ReadingsVal($name,$id."_email","")."</a>";;\
            $ret .= "</div>";; #/cell\
        $ret .= "</div>";; #/row\
        $ret .= "<div style=\"display:table-row;;width:100%;;\">";; #row\
            $ret .= "<div style=\"display:table-cell;;padding:3pt;;width:50%;;text-align:left;;\">";; #cell\
                $ret .= "Von ".ReadingsVal($name,$id."_start_date","")." ".ReadingsVal($name,$id."_start_time","");;\
            $ret .= "</div>";; #/cell\
            $ret .= "<div style=\"display:table-cell;;width:50%;;text-align:left;;\">";; #cell\
                $ret .= "Bis ".ReadingsVal($name,$id."_end_date","")." ".ReadingsVal($name,$id."_end_time","");;\
            $ret .= "</div>";; #/cell\
        $ret .= "</div>";; #/row\
    }\
    $ret .= "</div>";; #/table\
    return $ret;;\
}
viele Grüße, yersinia
----
FHEM 6.3 (SVN) on RPi 4B with RasPi OS Bullseye (perl 5.32.1) | FTUI
nanoCUL->2x868(1x ser2net)@tsculfw, 1x433@Sduino | MQTT2 | Tasmota | ESPEasy
VCCU->14xSEC-SCo, 7xCC-RT-DN, 5xLC-Bl1PBU-FM, 3xTC-IT-WM-W-EU, 1xPB-2-WM55, 1xLC-Sw1PBU-FM, 1xES-PMSw1-Pl