Archive for the ‘API’ tag
POST-Request mit mehreren gleichen Parametern verschicken
Ich hatte ein komisches Problem, bei dem ich auch erstmal nachdenken und nachlesen musste wie ich das gelöst bekomme. Folgende Ausgangssituation:
Ich musste einen POST-Request machen zu einem Webserver. Soweit kein Problem. Doch bei diesem Request war das besondere dass es mehrere POST-Parameter gab mit dem selben Namen. Beispiel:
attr=blue
attr=yellow
attr=green
amount=14
Zugriff auf die SlideShows bei SlideShare via Zend_Service_SlideShare
SlideShare hat eine API über die man auf die SlideShows zugreifen kann, man kann einzelne SlideShows abrufen und SlideShows nach Tags, Gruppen oder Usernamen suchen, und erhält dann jeweils Details wie Titel, Uploaddatum, Anzahl Views, Anzahl Downloads, EmbedCode und über ein Dutzend weitere Informationen. Es ist auch möglich neue SlideShows hochzuladen.
Der Zugriff ist nicht sonderlich schwer, man benötigt einen API-Schlüssel und ein Shared Secret die man bei SlideShare im Entwicklerbereich beantragen kann. Dann sendet man einfach GET bzw. POST Anfragen an bestimmte URLs, und bekommt XML zurück.
Am einfachsten geht es jedoch mit der Zend_Service_SlideShare Klasse aus dem Zend Framework:
Mit der eBay-API seine Auktionen beobachten
Nach dem letzten Artikel über die eBay-API und das Erstellen von Auktionen wollen wir nun unsere Auktionen beobachten und Informationen periodisch abfragen. Damit können wir uns selbst beispielsweise eine tägliche Übersicht senden oder die Auktionen auf unserer Webseite darstellen.
Die entsprechende API-Funktion heißt GetSellerList, sie liefert uns Informationen über unsere Auktionen eines bestimmten Zeitraums. Wenn man detaillierte Informationen über bereits verkaufte Artikel haben möchte muss man GetSellerTransactions nutzen. Hier stelle ich erstere vor.
Wie bereits im ersten Artikel stelle ich euch hier ein Basis-Script vor, natürlich sollte man das noch umbauen und erweitern, aber zur Erklärung der Funktionalität reicht es aus. Wir bereiten hier einen XML-Request vor mit den entsprechenden Parametern und erhalten eine XML-Antwort, die wir mittels DomDocument auseinander nehmen und die Informationen extrahieren. Hier also das Script:
<?php $addItem = new eBayGetSellerList(); $addItem->callEbay(); $addItem->printResult(); class eBayGetSellerList { private $_siteId = 77; // default: Germany private $_environment = 'sandbox'; // toggle between sandbox and production private $_eBayApiVersion = 661; private $_call = 'GetSellerList'; private $_keys = array( 'production' => array( 'DEVID' => '', 'AppID' => '', 'CertID' => '', 'UserToken' => '', 'ServerUrl' => 'https://api.ebay.com/ws/api.dll' ), 'sandbox' => array( 'DEVID' => '6daxxxxxxxxxxxxxxxxxxxxxxxxxx1e4622', 'AppID' => 'Mixxxxxxxxxxxxxxxxxxxxxxxxxxxxxx930', 'CertID' => '68xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx00e', 'UserToken' => 'AgAxxxxxxxxlaaaangxxxxxxxxxxIrGgYZ', 'ServerUrl' => 'https://api.sandbox.ebay.com/ws/api.dll' ) ); private function _getRequestBody() { $apiValues = $this->_keys[$this->_environment]; $dateNow = time(); $date4weeksAgo = $dateNow-60*60*24*28; $search = array( '%%USER_TOKEN%%', '%%EBAY_API_VERSION%%', '%%STARTTIMEFROM%%', '%%STARTTIMETO%%' ); $replace = array( $apiValues['UserToken'], $this->_eBayApiVersion, date('Y-m-d\TH:i:s.000\Z', $date4weeksAgo), date('Y-m-d\TH:i:s.000\Z', $dateNow) ); $requestXmlBody = file_get_contents('GetInfo.xml'); $requestXmlBody = str_replace($search,$replace, $requestXmlBody); return $requestXmlBody; } public function callEbay() { $apiValues = $this->_keys[$this->_environment]; $connection = curl_init(); curl_setopt($connection, CURLOPT_URL, $apiValues['ServerUrl']); curl_setopt($connection, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($connection, CURLOPT_SSL_VERIFYHOST, 0); $headers = array ( 'X-EBAY-API-COMPATIBILITY-LEVEL: ' . $this->_eBayApiVersion, 'X-EBAY-API-DEV-NAME: ' . $apiValues['DEVID'], 'X-EBAY-API-APP-NAME: ' . $apiValues['AppID'], 'X-EBAY-API-CERT-NAME: ' . $apiValues['CertID'], 'X-EBAY-API-CALL-NAME: ' . $this->_call, 'X-EBAY-API-SITEID: ' . $this->_siteId, ); curl_setopt($connection, CURLOPT_HTTPHEADER, $headers); curl_setopt($connection, CURLOPT_POST, 1); $requestBody = $this->_getRequestBody(); curl_setopt($connection, CURLOPT_POSTFIELDS, $requestBody); curl_setopt($connection, CURLOPT_RETURNTRANSFER, 1); $responseXml = curl_exec($connection); curl_close($connection); $this->_responseXml = $responseXml; var_dump($responseXml); } public function printResult() { //Xml string is parsed and creates a DOM Document object $responseDoc = new DomDocument(); $responseDoc->loadXML($this->_responseXml); //get any error nodes $errors = $responseDoc->getElementsByTagName('Errors'); //if there are error nodes if($errors->length > 0) { echo '<P><B>eBay returned the following error(s):</B>'; //display each error //Get error code, ShortMesaage and LongMessage $code = $errors->item(0)->getElementsByTagName('ErrorCode'); $shortMsg = $errors->item(0)->getElementsByTagName('ShortMessage'); $longMsg = $errors->item(0)->getElementsByTagName('LongMessage'); //Display code and shortmessage echo '<P>', $code->item(0)->nodeValue, ' : ', str_replace(">", ">", str_replace("<", "<", $shortMsg->item(0)->nodeValue)); //if there is a long message (ie ErrorLevel=1), display it if(count($longMsg) > 0) { echo '<BR>', str_replace(">", ">", str_replace("<", "<", $longMsg->item(0)->nodeValue)); } } else { //no errors //get results nodes $responses = $responseDoc->getElementsByTagName("GetSellerListResponse"); foreach ($responses as $response) { $acks = $response->getElementsByTagName("Ack"); $ack = $acks->item(0)->nodeValue; echo "Ack = $ack <BR />\n"; // Success if successful $totalNumberOfEntries = $response->getElementsByTagName("TotalNumberOfEntries"); $totalNumberOfEntries = $totalNumberOfEntries->item(0)->nodeValue; echo "totalNumberOfEntries = $totalNumberOfEntries <BR />\n"; $items = $response->getElementsByTagName("Item"); for($i=0; $i<$totalNumberOfEntries; $i++) { $itemId = $items->item($i)->getElementsByTagName('ItemID')->item(0)->nodeValue; $itemUrl = $items->item($i)->getElementsByTagName('ViewItemURL')->item(0)->nodeValue; $startTime = $items->item($i)->getElementsByTagName('StartTime')->item(0)->nodeValue; $endTime = $items->item($i)->getElementsByTagName('EndTime')->item(0)->nodeValue; $bidCount = $items->item($i)->getElementsByTagName('BidCount')->item(0)->nodeValue; $priceInEUR = $items->item($i)->getElementsByTagName('ConvertedCurrentPrice')->item(0)->nodeValue; $status = $items->item($i)->getElementsByTagName('ListingStatus')->item(0)->nodeValue; $title = $items->item($i)->getElementsByTagName('Title')->item(0)->nodeValue; $watchCount = $items->item($i)->getElementsByTagName('WatchCount')->item(0)->nodeValue; echo "itemID = $itemId <BR />\n"; echo "itemURL = $itemUrl <BR />\n"; echo "startTime = $startTime <BR />\n"; echo "endTime = $endTime <BR />\n"; echo "bidCount = $bidCount <BR />\n"; echo "priceInEUR = $priceInEUR <BR />\n"; echo "status = $status <BR />\n"; echo "title = $title <BR />\n"; echo "watchCount = $watchCount <BR />\n"; } } } } }
Die GetInfo.xml sieht folgendermaßen aus:
<?xml version="1.0" encoding="UTF-8"?> <GetSellerListRequest xmlns="urn:ebay:apis:eBLBaseComponents"> <RequesterCredentials> <eBayAuthToken>%%USER_TOKEN%%</eBayAuthToken> </RequesterCredentials> <ErrorLanguage>en_US</ErrorLanguage> <Version>%%EBAY_API_VERSION%%</Version> <GranularityLevel>Coarse</GranularityLevel> <IncludeWatchCount>true</IncludeWatchCount> <StartTimeFrom>%%STARTTIMEFROM%%</StartTimeFrom> <StartTimeTo>%%STARTTIMETO%%</StartTimeTo> <Pagination> <EntriesPerPage>200</EntriesPerPage> <PageNumber>1</PageNumber> </Pagination> <WarningLevel>High</WarningLevel> </GetSellerListRequest>
Bevor es funktioniert muß man natürlich noch seine API-Keys einfügen, siehe erster Artikel. Um die printResult() Funktion besser zu verstehen hier ein Bespiel des $responseXml:
<?xml version="1.0" encoding="UTF-8"?> <GetSellerListResponse xmlns="urn:ebay:apis:eBLBaseComponents"> <Timestamp>2010-04-3T11:25:37.003Z</Timestamp> <Ack>Success</Ack> <Version>661</Version> <Build>E661_INTL_BUNDLED_10949245_R1</Build> <PaginationResult> <TotalNumberOfPages>1</TotalNumberOfPages> <TotalNumberOfEntries>8</TotalNumberOfEntries> </PaginationResult> <HasMoreItems>false</HasMoreItems> <ItemArray> <Item> <AutoPay>false</AutoPay> <BuyerProtection>ItemIneligible</BuyerProtection> <Country>US</Country> <Currency>USD</Currency> <GiftIcon>0</GiftIcon> <HitCounter>NoHitCounter</HitCounter> <ItemID>110044434434</ItemID> <ListingDetails> <StartTime>2010-03-27T12:44:13.000Z</StartTime> <EndTime>2010-03-28T12:44:13.000Z</EndTime> <ViewItemURL>http://cgi.sandbox.ebay.de/ws/eBayISAPI.dll?ViewItem&item=110044434434&category=14111</ViewItemURL> <HasUnansweredQuestions>false</HasUnansweredQuestions> <HasPublicMessages>false</HasPublicMessages> <BuyItNowAvailable>true</BuyItNowAvailable> <ExpressListing>false</ExpressListing> </ListingDetails> <ListingDuration>Days_1</ListingDuration> <Location>San Jose, CA</Location> <PrimaryCategory> <CategoryID>14111</CategoryID> <CategoryName>Everything Else:Test Auctions:General</CategoryName> </PrimaryCategory> <Quantity>1</Quantity> <ReviseStatus> <ItemRevised>false</ItemRevised> </ReviseStatus> <SellingStatus> <BidCount>0</BidCount> <BidIncrement currencyID="USD">0.25</BidIncrement> <ConvertedCurrentPrice currencyID="EUR">1.26</ConvertedCurrentPrice> <CurrentPrice currencyID="USD">1.71</CurrentPrice> <MinimumToBid currencyID="USD">1.71</MinimumToBid> <QuantitySold>0</QuantitySold> <SecondChanceEligible>false</SecondChanceEligible> <ListingStatus>Completed</ListingStatus> </SellingStatus> <ShippingDetails> <TaxTable/> </ShippingDetails> <ShipToLocations>US</ShipToLocations> <Site>US</Site> <TimeLeft>PT0S</TimeLeft> <Title>TTTEST IN SANDBOX BEFORE PROD - DO NOT BID</Title> <WatchCount>0</WatchCount> <PostalCode></PostalCode> <PictureDetails> <PhotoDisplay>None</PhotoDisplay> </PictureDetails> <ProxyItem>false</ProxyItem> <BuyerGuaranteePrice currencyID="EUR">20000.0</BuyerGuaranteePrice> <ReturnPolicy> <ReturnsAcceptedOption>ReturnsNotAccepted</ReturnsAcceptedOption> <ReturnsAccepted>Returns Not Accepted</ReturnsAccepted> </ReturnPolicy> <PaymentAllowedSite>US</PaymentAllowedSite> </Item> ..... ..... </ItemArray> <ItemsPerPage>200</ItemsPerPage> <PageNumber>1</PageNumber> <ReturnedItemCountActual>8</ReturnedItemCountActual> </GetSellerListResponse>
Man erkennt natürlich einige Parallelen zur Auktionserstellung und kann das schön in eine Elternklasse auslagern.
Nachdem ich diese Funktion umgesetzt hatte, habe ich noch GetMyeBaySelling gefunden, mit der man die einzelnen Listen (ActiveList, BidList, DeletedFromSoldList, DeletedFromUnsoldList, ScheduledList, SoldList und UnsoldList) erhält, was natürlich sehr viel nützlicher ist.
Twitter-Applikationen mit Zend_Oauth
Kurz vorab: Ich möchte mich bei euch Lesern bedanken, ihr habt mich dazu gebracht hier nun den 100. Artikel zu verfassen. Außerdem folgen mir aktuell 98 Leute bei Twitter (vielleicht werden es ja heute 100?), wenn das kein Grund zu feiern ist! Danke euch dafür! Doch nun gehts los:
In den Kommentaren zu meinem Artikel über das Twittern via PHP wurde ich darum gebeten die Twitter-API-Benutzung auch mittels OAuth zu zeigen. Das werde ich hier nun tun, mit einer kleinen Erklärung zu OAuth und dann natürlich auch PHP-Code.
Doch was ist OAuth? Normalerweise nutzt man zur API-Kommunikation die Twitter-Login-Informationen. Nehmen wir an wir wollen einen kleinen Dienst im Internet anbieten, den andere Leute nutzen können. Dabei soll unser Dienst auf den Account der Nutzer zugreifen und Daten auslesen oder gar verändern. Damit wir den Dienst umsetzen können müssen unsere Nutzer also ihre Login-Usernamen und Passwörter bei uns eintragen. Damit verfügen wir über die volle Kontrolle über diese Accounts, könnten sie also auch löschen, das Passwort ändern usw. Da sicherheitsbewusste Benutzer ihr Passwort nicht hergeben, wird niemand unseren Dienst nutzen. Von dem Problem, dass der Nutzer bei einem Passwortwechsel auch alle Applikationen in Kenntnis setzen müßte mal ganz abgesehen.
OAuth ist nun eine Möglichkeit, unserer Applikation begrenzten Zugang zu den Accounts zu geben, aber nicht mittels dem normalen Login-Passwort, sondern einem extra Passwort (Access-Token) für die Kombination aus unserer Applikation und dem Nutzerkonto. Dieser Zugang kann dann auch beschränkt werden zum Beispiel auf nur lesenden Zugriff. Außerdem kann der Zugang wieder entzogen werden. Der Accountbesitzer behält also die volle Kontrolle über seinen Account.
Unser Vorgehen sieht nun wie folgt aus:
- Unsere Applikation benötigt von Twitter einen sogenannten Consumer-Key und ein Consumer-Secret. Das müssen wir uns von Twitter besorgen und identifiziert unsere Applikation später beim Token-Austausch. Außerdem wird beides dann zu einer Art Verschlüsselung beim Token-Austausch genutzt.
- Nachdem der potentielle Nutzer nun auf unsere Webseite gegangen ist müssen wir uns für ihn einen Token besorgen. Dazu holen wir uns einen temporären unauthorisierten Token von Twitter.
- Mit diesem unauthorisierten Token leiten wir den User auf eine Seite von Twitter, wo er dann gefragt wird, ob er unserer Applikation den begrenzten API-Zugriff erlauben möchte. Falls er das tut, wird er zurück auf unsere Seite geleitet
- Wir erhalten nun den authorisierten Token, den wir in einer Datenbank abspeichern. Ab jetzt können wir auf das Nutzerkonto zugreifen, und der Nutzer hat seine Twitter-Logininformation nicht preisgeben müssen.
Da diese OAuth-Kommunikation standardisiert ist und im Detail auch etwas komplizierter, werden wir das nicht selbst implementieren sondern die Zend_Oauth-Klasse nutzen. Seit Zend Framework 1.10 ist diese im Lieferumfang enthalten. Es gibt auch noch weitere OAuth-Klassen, die genutzt werden können. Danach werden wir in Zusammenspiel mit Zend_Service_Twitter auf den Account zugreifen und einen Tweet absetzen.
Fangen wir also an:
Um den Consumer-Key und das Consumer-Secret zu erhalten registrieren wir unsere neue Applikation: http://twitter.com/oauth_clients
Wir benötigen auch Write-Rechte, da wir ja einen Tweet absetzen möchten. Nach erfolgreicher Applikationsregistrierung erhalten wie die besagten beiden Informationen:
Nun schreiben wir unsere kleine Testapplikation. Darin sind die Punkte 2-4 abgehandelt. Der Einfachkeit halber speichern wir den ACCESS_TOKEN nur in der Session, normalerweise würde man diesen zu den Nutzerinformationen in die Datenbank schreiben.
<?php session_start(); require_once 'Zend/Oauth/Consumer.php'; $options = array( 'callbackUrl' => 'https://www.phpgangsta.de/twitter.php', 'siteUrl' => 'http://twitter.com/oauth', 'consumerKey' => '4c2jXXXXXXXXXXXXXXBkw', 'consumerSecret' => 'iPkXiOXXXXXXXXXXXXXXXXXXXXXXbSAJRhE', ); $consumer = new Zend_Oauth_Consumer($options); if (!isset($_SESSION['ACCESS_TOKEN'])) { if (empty($_GET)) { $token = $consumer->getRequestToken(); $_SESSION['REQUEST_TOKEN'] = serialize($token); $consumer->redirect(); } else { $token = $consumer->getAccessToken($_GET, unserialize($_SESSION['REQUEST_TOKEN'])); $_SESSION['ACCESS_TOKEN'] = serialize($token); unset($_SESSION['REQUEST_TOKEN']); } } if (isset($_SESSION['ACCESS_TOKEN'])) { $token = unserialize($_SESSION['ACCESS_TOKEN']); $client = $token->getHttpClient($options); require_once 'Zend/Service/Twitter.php'; $twitter = new Zend_Service_Twitter(); $twitter->setLocalHttpClient($client); $response = $twitter->status->update('Nachricht gesendet authentifiziert via OAuth'); }
Der Ablauf ist klar und wie oben beschrieben: Das Script holt sich einen Request-Token, der Nutzer wird dann auf die Twitter-Seite redirected und gefragt ob er den Zugriff erlauben möchte. Danach kommt er zurück auf unsere Seite (die wir bei der Registrierung angegeben haben, man kann diese auch mit dem Request abändern), wir holen uns aus den GET-Parametern den ACCESS_TOKEN und speichern ihn (in der Session). Danach können wir den TOKEN nutzen um zu twittern.
Dazu können wir entweder die oben beschriebene Methode nutzen und mit Hilfe des $token einen Http_Client erstellen den wir dann an den Twitter-Service geben. Dieser Client erledigt das Mitsenden der OAuth-Informationen bei jedem Request automatisch für uns. Oder aber wir nutzen den Http_Client direkt:
$client = $token->getHttpClient($options); $client->setUri('http://twitter.com/statuses/update.json'); $client->setMethod(Zend_Http_Client::POST); $client->setParameterPost('status', 'Nachricht gesendet authentifiziert via OAuth'); $response = $client->request();
Natürlich kann man die vergebenen Berechtigungen an Applikationen auch zurückziehen. Das geht auf der Seite https://twitter.com/account/connections:
Das war es auch schon für heute, ich hoffe das Prinzip von OAuth ist etwas klarer geworden und ihr könnt den Code nutzen um eigene Twitter-Applikationen zu erstellen.
Hier noch einige interessante Links zu (englischsprachigen) Tutorials und Übersichtsseiten:
WordPress Bilder optimieren mit WP-Smash.it und Zend_Http_Client
Die WordPress Mediathek wächst und wächst, ab und zu sind auch recht große Screenshots dabei oder gar Bilder der Digitalkamera, die häufig sehr groß sind und Metadaten enthalten.
Mit dem Service Smush.it von Yahoo kann man sehr einfach seine Bilder optimieren lassen. Man lädt die zu optimierenden Bilder hoch und erhält eine Auswertung, wieviel Prozent Smush.it sparen konnte (wohlgemerkt ohne die Bildqualität zu verschlechtern, es werden nur Metadaten entfernt sowie die Farbpalette angepasst usw.) und man kann die Bilder dann als zip-Datei herunterladen. Laut FAQ verwendet Smush.it intern aktuell ImageMagick, pngcrush, jpegtran und gifsicle und wählt das beste Ergebnis.
Der Service bietet auch eine API an. Um uns Bloggern das Leben einfacher zu machen gibt es das WordPress-Plugin WP-Smush.it, welches automatisch alle neuen Bilder, die man in die Mediathek hochlädt durch Smush.it optimieren kann. Auch bereits vorhandene Bilder kann man mit einem Klick optimieren.
Im Durchschnitt erhält man dabei Größenoptimierungen von 5-40%, also durchaus akzeptabel, und es passiert im Hindergrund, man muss nicht extra mit Bildprogrammen rumhampeln oder mit Konsolenprogrammen wie optiPNG und Konsorten hantieren.
Ein Problem hatte ich jedoch: Meine Mediathek enthielt bereits 121 Bilder. Da ich nicht 121 Mal den „Smush.it now“ Link in der Mediathek klicken wollte, habe ich mir ein kleines Script geschrieben was das für mich erledigt.
Vorgehensweise ist wie folgt: Ich brauche zuerst alle IDs der Bilder, die ich komprimieren kann, denn ein Aufruf zum Optimieren sieht so aus:
https://www.phpgangsta.de/wp-admin/admin.php?action=wp_smushit_manual&attachment_ID=34
Ich möchte also 121 mal diese URL aufrufen, jeweils mit den passenden IDs. Die IDs hole ich mir eben schnell via phpMyAdmin aus der Datenbank mit dem Query:
SELECT ID FROM `wp_posts` WHERE `post_type` = 'attachment' AND `post_mime_type` LIKE 'image/%'
Das Ergebnis exportiere ich als csv-Datei, sodass ich eine Datei erhalte in der jede Zeile exakt eine ID enthält.
Um die entsprechende WordPress-Plugin-URL aufzurufen werde ich Zend_Http_Client verwenden. Sieht dann so aus:
<?php $ids = file('ids.txt'); include 'Zend/Loader/Autoloader.php'; $autoloader = Zend_Loader_Autoloader::getInstance(); $client = new Zend_Http_Client(); // Zu Debugzwecken möchte ich das Ergebnis in lesbarer Form, sonst kommt evtl. gzip zurück $client->setHeaders(array( 'Accept-encoding' => '' )); // Timeout erhöhen, 10 Sekunden reichen nicht aus. $client->setConfig(array('timeout' => 60)); $client->setCookieJar(); // Erste Anfrage: einloggen und eine Session starten (Cookie wird in den CookieJar gepackt und bei den folgenden Requests immer mitgesendet) $client->setUri('https://www.phpgangsta.de/wp-login.php'); $client->setParameterPost('log', 'MeinAdministratorName'); $client->setParameterPost('pwd', 'MeinAdministratorPasswort'); $client->setParameterPost('rememberme', 'forever'); $client->setParameterPost('wp-submit', 'Anmelden'); $client->setParameterPost('redirect_to', 'https://www.phpgangsta.de/wp-admin/'); $client->setParameterPost('testcookie', '1'); $response = $client->request(Zend_Http_Client::POST); foreach ($ids as $id) { $id = trim($id); $client->setUri('https://www.phpgangsta.de/wp-admin/admin.php?action=wp_smushit_manual&attachment_ID='.$id); $response = $client->request(Zend_Http_Client::GET); echo 'finished'.$id."\n"; } echo "finished";
Zuerst müssen wir uns natürlich einloggen und speichern das Cookie im CookieJar. Erst danach können wir die URLs in einer Schleife aufrufen.
War eine schöne kurze Übung und garantiert interessanter und schneller als 121 Mal einen Link anzuklicken, wobei man zwischen jedem Klick 3-20 Sekunden warten muss, je nach Bildgröße.