18. October 2015: Twitter-Home als Feed mit Javascript

Vor gut zwei Jahren hat Twitter einen wichtigen Schritt zur Datensicherheit gemacht. Mit Einf├╝hrung ihrer API 1.1 wurde zum Ausf├╝hren vieler Funktionen eine OAuth Authentifizierung n├Âtig. Und bei der Implementierung in (Web-) Applikationen ist dies auch meistens kein Problem. Anders sieht es da schon mit JavaScript aus.

Generell ist von einer Implementierung von OAuth in JavaScript auch deutlich abzuraten! Da die privaten Schl├╝ssel zur einer eindeutigen Identifizierung im Klartext dem Client vorliegen m├╝ssen, gibt es keine M├Âglichkeit diese ausreichend vor Fremdzugriff zu sch├╝tzen. F├╝r mein letzte Projekt, eine personalisierte Browser-Startseite, war jedoch eine Server-seitige Implementation nicht zielf├╝hrend.

kbo Startpage

Beim Herausarbeiten der Funktionalit├Ąt stie├č ich immer wieder auf Aussagen das dies generell nicht m├Âglich sei (sogar von Twitter-Mitarbeitern). Selbstredend ist dies nicht korrekt; wenn auch die Implementation, vor allem dank des faszinierenden Signatur-Prozesses, durchaus herausfordernd ist. Um meinen Teil zum aufr├Ąumen mit diesem Ger├╝cht beizutragen, hier jedenfalls die Fr├╝chte meiner Arbeit.

Um das Skript auszuf├╝hren ben├Âtigt man:

  1. Eine Twitter-App (zu erstellen)
  2. Authentifizierten Zugriff der App auf das eigene Profil
  3. Die CryptoJS library (hmac-sha1.js reicht)
  4. jQuery (zumindest in dem Beispiel)

F├╝llt man die Variablen f├╝r Consumer und Access Token aus, sollte der Aufruf von loadTwitter() das Array tweets[] mit den letzten Tweets der Twitter Home-Timeline bef├╝llen. Und die kann man dann, zum Beispiel, in einer personalisierten Startseite verwenden.

/*
 * Retrieve Tweets from your personal Twitter home via REST.
 * (c)2015 by Kai Boenke [code@boenke.info]
 * Requires CryptoJS (hmac-sha1.js): https://code.google.com/p/crypto-js/
 * Requires jQUery: https://jquery.com/
 */
var tweets = [];
function loadTwitter(){
	twitterURL =			"https://api.twitter.com/1.1/statuses/home_timeline.json";
	twitterURLmethod =		"GET",
	twitterKey =			"xxxxxxxxxxxxxxxxxxxxxxxxx"; //Consumer Key (User)
	twitterSecret =			"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Consumer Secret (User)
	twitterToken =			"99999999-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Access Token (App)
	twitterTokenSecret =	"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; //Access Token Secret (App)
	twitterNonce =			getNonce(32);
	twitterTimestamp =		Math.round(Date.now()/1000);
	
	// Generate oAuth Signature (https://dev.twitter.com/oauth/overview/creating-signatures)
	twitterParameter = [
		encodeURI('oauth_consumer_key='+twitterKey),
		encodeURI('oauth_nonce='+twitterNonce),
		encodeURI('oauth_signature_method='+"HMAC-SHA1"),
		encodeURI('oauth_timestamp='+twitterTimestamp),
		encodeURI('oauth_token='+twitterToken),
		encodeURI('oauth_version='+"1.0")
	];
	twitterParameter.sort();
	twitterSignatureBase =	twitterURLmethod +"&"+ encodeURIComponent(twitterURL) +"&"+ encodeURIComponent(twitterParameter.join("&"));
	twitterSignatureKey =	encodeURIComponent(twitterSecret) +"&"+ encodeURIComponent(twitterTokenSecret);
	twitterSignature =		encodeURIComponent(CryptoJS.HmacSHA1(twitterSignatureBase, twitterSignatureKey).toString(CryptoJS.enc.Base64));
	
	// Get tweets
	$.ajax({
		type:		twitterURLmethod,
		url:		twitterURL,
		headers:	{
			'Authorization': 'OAuth '+
				'oauth_consumer_key="'+ twitterKey +'", '+
				'oauth_nonce="'+ twitterNonce +'", '+
				'oauth_signature="'+ twitterSignature +'", '+
				'oauth_signature_method="HMAC-SHA1", '+
				'oauth_timestamp="'+ twitterTimestamp +'", '+
				'oauth_token="'+ twitterToken +'", '+
				'oauth_version="1.0"'
		}
	}).done(function(twitterData){
		// Retrieve tweets
		$.each(twitterData, function(i, tweet){
			tweets.push({
				timestamp: (new Date(tweet.created_at)).getTime(),
				link: "https://twitter.com/"+ tweet.user.screen_name +"/status/"+ tweet.id_str,
				value: tweet.text
			});
		});
	});
}

25. September 2013: Sticky-Navigation in Prototype

Sticky-Navigationen sind ja irgendwie ein Trend: Die Navigationsleiste einer Webseite bleibt dabei am oberen Fensterrand kleben, wenn man nach unten scrollt. Ist ja auch eine praktische Sache, manchmal jedenfalls.

Die g├Ąngigen Implementierungen greifen jedoch auf jQuery zur├╝ck. Soweit nichts verwerfliches, ist es doch eine weit verbreitete Standard-Bibliothek. F├╝r die Implementierung auf diesem Blog wollte ich jedoch die ohnehin bereits verwendete Bibliothek Prototype nutzen. Doch leider gab es dazu keine L├Âsung. Oder zumindest konnte ich keine finden. F├╝r die nachfolgenden Leidensgenossen hier ein passende L├Âsung:


12. September 2013: Bubble-Sort in PHP

Da bin ich die Tage doch glatt dazu gekommen einige (Er-)Kenntnisse meines Algorithmus-Kurses vom letzten Jahr anzuwenden. Grund war die fehlende Funktion zur Sortierung eines Mehrdimensionalen Arrays basierend auf einem Sub-Key. Aber gut, so konnte ich mir wenigstens beweisen das ich das Konzept noch verstanden habe:

// (Bubble)Sort
do{
	$swapped = false;
	for($i=0; $i<(count($calendar)-1); $i++){
		if($calendar[$i]["stamp"] > $calendar[$i+1]["stamp"]){
			$swap = $calendar[$i];
			$calendar[$i] = $calendar[$i+1];
			$calendar[$i+1] = $swap;
			$swapped = true;
		}
	}
}while($swapped != false);

Mal schauen wann mir dann eine Anwendung f├╝r die Algorithmen zu Graphen in den Schoss f├Ąllt.

PS: Wieso gibt es in PHP eigentlich kein array_swap()?

14. July 2011: Warum kompliziert...

Es gibt Tage, an denen man die Welt nicht mehr versteht. Letztens war einer dieser Tage. Ich programmierte ein kleines Tool um Labels auf einem Zebra Drucker auszugeben. Dachte ich Anfangs noch das die Erstellung des Layouts in ZPL die gr├Â├čte Herausforderung werden w├╝rde, wurde ich alsbald eines besseren belehrt.

ZPL – das sei an dieser Stelle ausdr├╝cklich erw├Ąhnt – ist eine schn├Ârkellose, schnell erlernbare Beschreibungssprache. Hat man sich erst einmal an die Syntax gew├Âhnt, kann man in k├╝rzester Zeit ansprechende Layouts erstellen. Das man die Anweisungen in Klartext via TCP/IP direkt an den Drucker ├╝bergeben kann, macht es auch sehr angenehm die Schnittstelle in eigenen Programmen zu implementieren – ganz gleich welche Sprache man benutzt.

So verwundert es im Nachhinein nicht wirklich, dass dies nicht die bef├╝rchtete Herausforderung darstellte. Nun sollte das Tool noch Daten aus einem MS SQL Server laden um diese in das Label-Layout einzuf├╝gen. Es ist nicht mein erstes Programm, dass Daten aus einem MS SQL Server laden sollte – und um die Dinge noch einfachere zu machen w├Ąhlte ich VB.NET als meine Programmiersprache. Doch hier begann die Verzweiflung zuzuschlagen…

Da ich keinen direkten Zugriff auf die Datenbank hatte, musste ich den Umweg ├╝ber die SSRS gehen. Naiv wie ich bin, ging ich davon aus das dies kein Problem darstellen sollte – gibt es doch entsprechende Objekte und Klassen im .NET Framework. Und siehe da: Man kann sogar mit wenigen Mausklick eine Referenz auf einen SSRS Server anlegen (wenn man die URL kennt und manuell entsprechend aufbereitet):

ReportingService2005 Web Reference

Aber… da habe ich wohl zu einfach gedacht. Denn trotz mannigfaltiger Ans├Ątze, dem quer-lesen dutzender Artikel und des testens nahezu jeder Funktion der ReportingService2005-Klasse musste ich nach zwei Tagen aufgeben. Zwar konnte ich den Server ansprechen und auch diverse Aktionen durchf├╝hren – allein Daten auszulesen war mir nicht m├Âglich. Und darum ging es ja nun schlie├člich.

Also besann ich mich auf eine althergebrachte Methode: XML. Und ein XML-Export stellt f├╝r die SSRS kein Problem dar (wenn man die URL entsprechend manuell aufbereitet). Hier also meine kleine Funktion f├╝r all jene, die gegebenenfalls in dieselbe Bredouille geraten:

Public Function LoadSsrsData() As Boolean
	Dim url As String = _
	 + _
	 + _
	"&rs:Command=Render&rs:Format=XML&" +  + "=" + 
	Dim httpReq As WebRequest = _
	WebRequest.Create(url)
	With httpReq
		.Credentials = CredentialCache.DefaultCredentials
	End With

	Dim httpDoc As WebResponse
	Try
		httpdoc = httpReq.GetResponse()
	Catch ex As Exception
		Debug.Print(ex.Message)
		Return False
	End Try

	Dim xmlDoc As New XmlDocument()
	Dim xmlNodes As XmlNodeList
	Dim xmlNode As XmlNode
	Dim xmlAttr As XmlAttribute
	Try
		xmlDoc.Load(httpDoc.GetResponseStream())
		Debug.Print(xmlDoc.InnerXml)
	Catch ex As Exception
		Debug.Print(ex.Message)
		Return False
	End Try

	Return True
End Function

15. January 2010: Google-Maps Suche

Schon interessant, wie man manchmal bei eigentlich selbstverst├Ąndlichen Sachen forschen und tricksen muss. Zuletzt bei der Anzeige von diversen Adressen in einer Karte von Google-Maps. Hier sollte der Kartenausschnitt auf die angezeigten Ergebnisse zentriert und der Zoom entsprechend gesetzt werden. Standard-Funktionen gibt es daf├╝r leider nicht, aber mit einem kleinen Trick funktioniert es dennoch recht geschickt:

var map = new GMap2(document.getElementById("map"));
var geocoder = new GClientGeocoder();
map.setCenter(new GLatLng(25.783662, -80.189466), 9);
var viewport = new GLatLngBounds();
GEvent.addListener(map, 'markeradded', function(){
	map.setCenter(viewport.getCenter());
	map.setZoom(map.getBoundsZoomLevel(viewport)-1);
	});
geocoder.getLatLng("1900 N BAYSHORE DR  4109, Miami FL 33132", function(point){
	var marker = new GMarker(point);
	GEvent.addListener(marker, 'click', function(){
		marker.openInfoWindowHtml("1900 N BAYSHORE DR  4109, Miami FL 33132
Show Details"); }); map.addOverlay(marker); if(!viewport.contains(point)){ iewport.extend(point); } GEvent.trigger(map, 'markeradded'); });

Der Trick besteht darin im Eventhandler der Marker den hinzugef├╝gten Punkt einem GLatLngBounds-Objekt zu ├╝bergeben. Mit Hilfe dieses Objekts kann der Kartenausschnitt neu angepasst werden.

Interessant ist dabei, dass panTo() durch den direkt anschlie├čenden Aufruf von setZoom() nicht korrekt funktioniert. Mit setCenter() gibt es allerdings keine Probleme.

8. July 2009: Web-Reiniger

AJAX (Asynchronous JavaScript and XML) war auch so einer Neuerung in der Webentwicklung, die ich verpasst zu haben dachte. Tolle Sachen wurden damit gemacht. Web 2.1 k├Ânnte man sagen (und dr├╝ber streiten). Dabei ist das aller erschreckend einfach gemacht:

/* Create AJAX-Object */
try{
	h = new XMLHttpRequest();
}
catch (e){
	try{
		h = new ActiveXObject("Msxml2.XMLHTTP");
	}
	catch (e){
		try{
			h = new ActiveXObject("Microsoft.XMLHTTP");
		}
		catch (e){
			return(false);
		}
	}
}

/* Send Data */
h.open('POST', u, true);
h.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
h.setRequestHeader("Content-length", data.length);
h.setRequestHeader("Connection", "close");
h.onreadystatechange = function(){
	if(h.readyState == 4 && h.status == 200){
		toggleVisibility('processing');
		e = h.responseText;
		if(e == "1"){
			toggleVisibility('success');
		}else{
			toggleVisibility('failure');
			document.getElementById('errormessage').innerHTML = e;
		}
	}
}
h.send(data);

Gef├╝hlt w├╝rde ich ja sagen, dass das funktionieren eher auf Gl├╝ck und User-Vertrauen (samt aktuellem Browser) basiert. Aber der Erfolg dergleichen spricht ja eher eine andere Geschichte.

8. July 2009: Der alte K(r)ampf

Zugegeben, es ist schon einige J├Ąhrchen her das ich mich intensiver mit HTML auseinandersetzen musste. CSS war seinerzeit eine tolle Sache, die sich gerade durchzusetzen abzeichnete, Internet Explorer war der Browser schlechthin und Firefox nannte sich noch Phoenix. Und schon seinerzeit war es eine Herausforderung sondergleichen eine Webseite so zu schreiben, dass sie auf allen Browsern in etwa gleich aussah – oder zumindest zumutbar.

Das hat sich bis heute leider nicht wirklich ge├Ąndert. Auch wenn es wesentlich leichter geworden ist sein Ziel zu erreichen. Immerhin kann man jetzt einfach ein zus├Ątzliches Stylesheet schreiben, dass IE-spezifische ├änderungen vornimmt.


Tolle Sache!

4. February 2009: Textpattern bei 1und1

Nachdem diese Domain ja mittlerweile bei ihrem neuen Hoster angekommen ist, habe ich heute dann auch den letzten Fehler behoben: Saubere URLs in Textpattern. Ein wenig kniffelig, aber durchaus einfach zu l├Âsen. Mit folgender .htaccess-Datei:

DirectoryIndex index.php index.html
# For security reasons, Option followsymlinks cannot be overridden.
#Options +FollowSymLinks
Options +SymLinksIfOwnerMatch
RewriteBase /

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -f [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^(.+) - [PT,L]
RewriteRule ^(.*) index.php

8. November 2006: Lokalisierung: EqDKP-Raid Progression

So ein DKP-System ist schon was feines: Je nachdem an wie vielen Raids man teilgenommen hat, hat man auch bessere/schlechtere Chancen an ein begehrtes Item zu gelangen. Die Implementation beim Kreis der Macht ist, nicht zuletzt durch einen sehr vernarrten Gildenleiter, auch entsprechend aufgebohrt – schlie├člich ist es die Statistik-Seite Nummer Eins der Gilde.

Eine der neuesten Erweiterungen, die eingebunden werden sollte, war das PlugIn Raid Progression zur Verfolgung des Fortschritts in Instanzen. Leider Gottes kam es jedoch nicht mit unserer Art des Eintragens von Raids zurecht. Ein Problem, das scheinbar auch andere Leute haben:

looks nice.
is there a change to modify the plugin, so ite can handel with other Raidnotes like this http://www.seniorenraid.de/set/listraids.php

Nun, die L├Âsung soll niemandem vorbehalten bleiben:

// Extension start

if(empty($row['raid_note']))   continue;

$elements = explode(" ", str_replace(", ", " ", $row['raid_note']));
foreach($elements as $killed_mob)
   foreach($instance as $event => $mobs)
      foreach($mobs as $mob => $count)
         if(@strstr($mob, $killed_mob))
            $instance[$event][$mob] = $row['raid_id'] . ':' . $row['event_id'];

/* Old code
   if ( in_array_multi_key($row['raid_note'], $instance) )
   {

      $instance[$row['raid_name']][$row['raid_note']] = $row['raid_id'] . ':' . $row['event_id'];
   }
*/
// Extension end

Einzubauen ist das ganze in die Datei /dkp/plugins/rp/index.php. Die Stelle sollte sich dem geneigten Leser aus dem auskommentierten Code von selbst erschlie├čen.

7. November 2006: Serialisierte Instanzen

Abw├Ąrtskompatibilit├Ąt ist eine tolle Sache. Sie verspricht, dass man die Entwicklungen auf der momentanen Version auch in Zukunft noch nutzen kann und dadurch nicht laufend mit der Wartung seiner alten Programme besch├Ąftigt ist.

Was ich jedoch gestern erfahren musste, hat immer noch einen faden Beigeschmack. Es ging darum, dass eine Webseite mit komplexen adhoc-Berechnungen die zugrunde liegenden Instanzen eines Objektes in einer Datenbank-Tabelle zwischenspeichern sollte. Nichts leichter als das:

$so = serialize($o);

Das Zur├╝ckholen in den Applikationskontext zu einem sp├Ąteren Zeitpunkt ging ebenso leicht:

$o = unserialize($so);

In meiner Test-Umgebung (PHP 4.3.3) hat das alles wunderbar funktioniert. Toll, dachte ich, dass das so einfach geht. Doch zu fr├╝h gefreut. In der Produktiv-Umgebung (PHP 4.4.4) fehlten den so wieder ins Leben zur├╝ck gerufenen Instanzen auf einmal die Methoden, was neben unsch├Ânen Fehlermeldungen auch zu einem Ausfall der Anwendung gef├╝hrt hat. Laut PHP-Dokumentation soll man, um genau dies zu vermeiden, eine kleine Korrektur vornehmen:

$so = unserialize(implode("", $so));

Leider f├╝hrte auch das nicht zum gew├╝nschten Ergebnis. Blieb also nur der Biss in den sauren Apfel und ein entsprechend ausschweifender Work-Around (die Daten werden jetzt in einer eigenen Cache-Tabelle zwischengespeichert).