Schlagwort-Archive: Google

Handy-History (Teil 2)

Vor fast acht Jahren habe ich zum ersten uns bisher letzten Mal eine Übersicht meiner bis dahin verwendeten Handys und Smartphones erstellt. Anlass war das damals brandneue HTC Desire HD. Ein wirklich sensationelles Gerät. Ich weiß noch gut, wie ich darauf gewartet habe, dass endlich ein Android-Smartphone mit diesem Formfaktor auf den Markt kommt. Und bei meinem Gedächtnis will das etwas heißen.

Es wird Zeit, diese Liste fortzuführen und einen groben Überblick der Geräte zu erstellen, die in meiner Hosentasche dem HTC Desire HD nachgefolgt sind. Durch das Desire HD bin ich ein kleiner HTC-Fan geworden, auch wenn ich seither kein HTC-Gerät mehr besessen habe. Der Grund: 2008 kam Google mit seiner Nexus-Serie auf den Markt. Das erste Modell, das Nexus One, wurde von HTC gebaut, und damit war ich auf der Nexus-Spur. Ich fand die Idee sympatisch, Geräte mit „reinem Android“ zu vertreiben und war dann ab dem Nexus 4 mit dabei.

März 2013: Nexus 4

Google hatte nach Erscheinen Ende Oktober 2012 massive (und seitdem legedäre) Lieferschwierigkeiten mit dem Nexus 4. Es hat bis März 2013 gedauert, bis ich meines endlich hatte. Es war die größere 16GB-Version. Was mir von Anfang an an der Nexus-Reihe gefiel war die Schlichtheit der Geräte: Kein Logo auf der Fronseite. Kein Home-Button, wie er bei Samsung und Apple lange üblich war und teils noch üblich ist. Konnte ich nie leiden, flexibel belegbare Softbuttons, die verschwinden und Platz für andere Inhalte machen konnten schienen mir schon immer die sinnvollere Wahl. Weniger schlicht war die glizernde Glas-Rückseite. Sah schon sehr schick aus, war aber etwas rutschig, was aber der gummierte Rahmen aber einigermaßen wett gemacht hat. Es gab damals noch kein Wort für das, was heute „Screen-to-Body-Ratio“ genannt wird, aber schaut man sich das Nexus 4 an war es in diesem Punkt seiner Zeit weit voraus. Gefertigt wurde das Gerät von LG, wie auch die nächsten Geräte der Nexus-Serie. Darauf komme ich weiter ungen noch zu sprechen. Mein Nexus 4 hatte ich ca. zweieinhalb Jahre, es wurde dann familienintern weitergereicht.

November 2015: Nexus 5

Im Herbst 2015 ist bei mir der Nachfolger aufgeschlagen, das Nexus 5, und zwar in der schicken weißen 32GB-Variante. Es war in technischer Hinsicht ein logischer Nachfolger des Nexus 4. Das Gerät war mir anfangs definitv zu teuer, aber über eine Angebotsaktion eines großen Elektronikhauses kam ich bei einer „Kaufe zwei für den Preis von einem“-Aktion einigermaßen erschwinglich an das Gerät. Besser gesagt an zwei davon. Allerdings war das dann schon ca. 2 Jahre nach Erscheinungsdatum. Bis heute finde ich das Nexus 5 optisch nicht nur das gelungenste aus der Nexus-Reihe, sondern auch im Vergleich mit heutigen Geräten immer noch sehr ansehlich. Leider hat eines der beiden von uns verwendeten recht schnell den Geist aufgegeben, und aufgrund der besonderen Kaufsituation konnte kein Ersatz beschafft, sondern nur der Zeitwert erstattet werden. Naja, das andere läuft dafür heute noch recht problemlos, wenn auch nicht bei mir. Ein tolles Gerät, bei dem sehr viel richtig gemacht wurde.

Mai 2016: Nexus 5X

Im Mai 2016 fragte mich ein Freund, was ich für ein Handy kaufen würde, wenn ich jetzt eines bräuchte. „Das Nexus 5X“ war die sponate und ehrliche Antwort. Oder mein gebrauchtes Nexus 5 kaufen, meinte ich so halb im Spaß. Am nächsten Morgen schrieb er mir, dass er meines nehmen würde, und so war ich unter Zugzwang… Ich habe mich auf Ebay umgesehen und ein nagelneues Nexus 5X in gewünschter Farbe („ice blue“) gekauft. Geliefert wurde es in einem Päckchen, das heute noch der ganzen Familie in Erinnerung ist. Da war ein schon recht alter Mensch offensichtlich sehr dankbar, etwas losbekommen zu haben, womit er nichts anfangen konnte. Das Gerät war originalverpackt, außerdem in Geschenkpapier eingewickelt, es lagen Pralinen und eine Art Dankesbrief bei.

Das Gerät war wieder etwas gewachsen, die Optik auf der Vorderseite hatte sich wenig verändert, die Rückseite schon: Erstmals gab es in der Nexus-Reihe einen Fingerabdruck-Sensor, außerdem hat man die Asymmetrie der Rückseite aufgegeben. Auch aufgegeben hat man die drahtlose QI-Lademöglichkeit, den Grund habe ich nie verstanden.

Alles Nexus-Geräte, die ich hatte, wurden von LG hergestellt, und alle hatten ihre technischen Probleme: Das Nexus 4 mussten in seiner Laufzeit einmal von Google ausgetauscht werden, eines unserer Nexus 5-Geräte ging recht schnell kaputt, und das Nexus 5X wurde sogar zweimal von Google getauscht und hat jetzt – Ende März 2018 – leider außerhalb der Garantizeit dann endgültig seinen Geist aufgegeben. Google verhält sich beim Austausch vorbildlich, das muss man sagen, trotzdem ist das eine deutlich zu hohe Quote, die aber wohl eher dem Hersteller LG anzulasten ist. Was beim 5x auffällig war: Zweimal ging das Gerät beim ersten Anstecken am Original-Ladegerät im Ausland kaputt, einmal in Frankreich, einmal in Tschechien. Irgendwie irritierend.

Nachtrag: Google hat das Gerät tatsächlich ein drittes Mal getauscht. Das neue ist jetzt bei der Schwiegermutter (72) im Einsatz. Es ist ihr erster „Wischkästla“…

März 2018: OnePlus 5T

Nach dem spontanen Ausfall meines Nexus 5X mussten schnell Ersatz her, es war wenig Zeit und Muse für eine ausführliche Suche. Schnell ist immer schlecht bei solchen Dingen. Von Anfang an beobachte ich die Smartphones der Firma OnePlus und hege eine gewisse Sympathie für deren Hardware. Gestört hat mich tatsächlich bei den dirketen Vorgängermodellen der Button auf der Fronseite, aber genau der war zum ersten Mal beim Modell 5T verschwunden und dafür das Display gewachsen. Grünes Licht.

Das Problem: Vom aktuellen Gerät – dem Oneplus 5T – waren kaum mehr welche zu bekommen. Bei Amazon komplett ausverkauft, auch bei anderen Händlern, nur noch über ihre eigene Webseite war die große Version mit 128GB Speicher und 8GB Ram noch zu haben. Also hurtig bestellt, und ich muss sagen, das Ding ist schon wirklich nicht übel. Unglaublich schnell, unglaublich lange Akkulaufzeit (noch nie nicht durch den Tag gekommen, auch bei sehr intensiver Nutzung), gutes Display, ziemlich gute Kamera, ziemlich unverbautes Android. OK, das Design haut mich nicht um, es fehlt einfach das besondere Etwas, was die Nexus-Geräte in meinen Augen immer hatten. Richtig hässlich ist es aber auch nicht. Ich denke, wir könnten Freunde werden.

OAuth 2.0 – Lord, have mercy!

OAuth 2.0

Es gelingt mir nicht immer, auf dem Laufenden zu bleiben, was technische Entwicklungen im Web angeht. Vorsichtig ausgedrückt. Obwohl ich mich durchaus bemühe. Aber da draußen ist einfach zu viel los. So ging es auch an mir vorbei, dass Google angekündigt hatte, seinen Client-Login im April 2015 abschalten zu wollen. Oder ich habe einfach nicht verstanden, dass mich das betrifft. Hat es mich aber, und das gleich an verschiedenen Stellen.

Zum einen nutze ich an verschiedenen Stellen im Netz die Möglichkeit, Daten aus Google Tabellen auszulesen und diese weiter zu verarbeiten. Dieser Weg hat im Gegensatz zur Nutzung einer Datenbank den Charme, dass auch eine technisch nicht so versierte Gruppe von Usern sehr leicht Daten editieren kann, die dann direkt auf irgendwelchen Webseiten dargestellt oder für andere Dienste verarbeitet werden können.

Zum anderen brauchte ich diese Art der Authentifizierung für das Backup meiner Google Kontakte auf dem Raspberry Pi. Ich hatte darüber mal ausführlich geschrieben.

Die Nutzung des Client-Logins sah (vereinfacht) so aus, dass man sich bei Google-APIs mit seinen Google-Zugangsdaten authentifizieren konnte. Einen Schritt besser war es schon, dazu eine sog. „anwendungsspezifisches Passwort“ zu verwenden. D.h. man generierte sich bei Google ein weiteres Passwort, welches dann nur eine einzige Anwendung verwenden durfte. Vorteil: Durch Löschen des vergebenen Passworts war auch der Anwendung der Zugriff auf die Daten entzogen, außerdem musste man nicht sein richtiges Google-Passwort in irgendwelchen Skripten hinterlegen. Umgesetzt hatte ich das alles mit Hilfe des Zend Frameworks.

Diese Möglichkeit gibt es jetzt nicht mehr. OAUth 2.0 ist angesagt. OAuth ist an sich nichts neues, aber das Ding ist einfach deutlich komplexer als die herkömmliche Authentifizierung. Wie OAuth 2.0 funktioniert genau funktioniert erkläre ich hier nicht weiter, man kann das z.B. hier schön nachlesen. Ein Artikel in 5 Teilen, das sagt schon alles. Die Grundidee ist die, dass eine Anwendung einen z.B. auf eine Google-Seite weiterleitet, man sich dort authentifiziert und somit seiner Anwendung bestimmte Rechte zuweist, ohne dass die Anwendung Details der Zugangsberechtigung erhält. Das typische „Loggen Sie sich einfach mit Facebook ein“-Ding. Der große Vorteil ist hier, dass keine Passwörter weitergegeben werden, sondern lediglich sog. Access-Tokens generiert werden, die in der Regel auch eine Verfallsdatum haben. Alles crispy, alles funky, aber eben auch alles etwas komplizierter.

Wie gesagt war der Anlass, mich wieder mal mit dem Thema zu befassen, die Reaktivierung des Backups meiner Google Kontakte mit dem Raspberry Pi. Deshalb werde ich hier mal kurz umreißen, wie das Prozedere jetzt aussieht.

Der PHP-Code

Mir ist klar, dass sich meine PHP-Künste im Rahmen halten. Der Code hier ist also nur zum Hausgebrauch und als Arbeitsgrundlage für weitere Optimierungen geeignet. Außerdem enthält er eine Reihe an Kommentaren, die eher den Stil eines Selbstgesprächs als einer sinnvollen Code-Kommentierung haben. Egal.


<?

		// Backup der Google Kontakte per Cronjob funktioniert nur noch mit OAuth2

		// Es gäbe jetzt 2 Wege:

		// 1) User authentifiziert sich per Web

		// 	Problem:
		// 	- Die Authentifizierung läuft schnell ab.
		// 	- Daher ist das per Skript nicht praktikabel.

		// 2) Authentifizierung über ein "Dienskonto" ("service account") mit einem P12-Schlüssel

		// 	Problem:
		// 	- Das Dienstkonto mit seiner speziell generierten Mailadresse kann sich zwar dauerhaft authentifizieren,
		// 	  hat aber keinen Zugriff auf die Kontakte des Users, der das Dienstkonto erstellt hat...

		// Hinweise:

		// - Erstellen eines Projekts bei Google und erzeugen der Schlüssel:		https://console.developers.google.com/project
		// - API Client Library for PHP:											https://developers.google.com/api-client-library/php/start/get_started
		// - Google Contacts API version 3.0:										https://developers.google.com/google-apps/contacts/v3/index#retrieving_all_contacts
		// - google-api-php-client:													https://github.com/google/google-api-php-client
		// - Beispielcode für Ahthentifizierung per Web:							https://github.com/google/google-api-php-client/issues/462
		// - Warum es nicht geht, sondern nur mit einer "Google managed domain":	http://stackoverflow.com/questions/14407415/accessing-google-contacts-api-via-oauth-2-0-and-private-key-aka-service-account
		// - Vielleicht geht es doch per Web-Authentifizierung???					http://stackoverflow.com/questions/13851157/oauth2-and-google-api-access-token-expiration-time

		//  >>> However, after a successful completion of the OAuth2 installed application flow,
		// 		you will get back a refresh token.
		// 		This refresh token never expires, and you can use it to exchange it for an access token as needed.
		// 		Save the refresh tokens, and use them to get access tokens on-demand
		// 		(which should then immediately be used to get access to user data).

# Debug-Modus. Wenn der TRUE ist werden nur Debug-Infos rausgeschickt:
define("DEBUGMODUS", FALSE);

# # # # VERSION MIT DER WEB-AUTHENTIFIZIERUNG

	# Diese Daten hier erhält man nach der Registrierung eines Projekts in der Developers Console (https://console.developers.google.com/):
	# Client-ID:
	$client_id 		= 'HIER KOMMT DEINE CLIENT-ID REIN';
	# Clientschlüssel:
	$client_secret 	= 'HIER KOMMT DEIN CLIENT-SCHLÜSSEL REIN';

	# Nach dem ersten Aufruf des Skripts per Webbrowser bekommt man einen RefreshToken,
	# den man hier einträgt. Mit diesem kann dann bei jedem weiteren Aufruf
	# ein neuer AccessToken geholt werden, mit dem dann wiederrum die Daten geholt werden:
	$refresh_token = 'HIER KOMMT DEIN REFRESH-TOKEN REIN';

	/*
	 * Copyright 2011 Google Inc.
	 *
	 * Licensed under the Apache License, Version 2.0 (the "License");
	 * you may not use this file except in compliance with the License.
	 * You may obtain a copy of the License at
	 *
	 *     http://www.apache.org/licenses/LICENSE-2.0
	 *
	 * Unless required by applicable law or agreed to in writing, software
	 * distributed under the License is distributed on an "AS IS" BASIS,
	 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	 * See the License for the specific language governing permissions and
	 * limitations under the License.
	 */

	# Falls das Skript per Kommandozeile ausgeführt werden soll sollen die Fehler unterdrückt werden (z.B. die Warnings wg. der Session):
	if ($refresh_token)	error_reporting(0);

	session_start();

	// # Session manuell killen, und damit neue Token erzwingen:
	// unset($_SESSION['access_token']);

	# Diese Library muss eingebunden werden: https://github.com/google/google-api-php-client
	require_once __DIR__ . '/google-api-php-client-master/examples/templates/base.php';
	require_once __DIR__ . '/google-api-php-client-master/src/Google/autoload.php';

	$scriptUri 		= "http://".$_SERVER["HTTP_HOST"].$_SERVER['PHP_SELF'];
	$redirect_uri 	= $scriptUri;

	/************************************************
	  Make an API request on behalf of a user. In
	  this case we need to have a valid OAuth 2.0
	  token for the user, so we need to send them
	  through a login flow. To do this we need some
	  information from our API console project.
	 ************************************************/
	$client = new Google_Client();
	$client->setClientId($client_id);
	$client->setClientSecret($client_secret);
	$client->setRedirectUri($redirect_uri);
	//$client->addScope("https://www.googleapis.com/auth/contacts.readonly");
	$client->addScope("https://www.google.com/m8/feeds");

	# HIER KOMMT DER ENTSCHEIDENDE PUNKT!
	# Der AccessToken soll OFFLINE sein, d.h. ich bekomme auch einen 'refresh_token',
	# mit dem ich mir dann immer wieder neu einen 'access_token' holen kann,
	# ohne die Web-Authentifizierung neu machen zu müssen.
	# Siehe https://developers.google.com/identity/protocols/OAuth2WebServer#refresh
	# PHP-Demo: http://stackoverflow.com/questions/9241213/how-to-refresh-token-with-google-api-client
	# Außerdem muss dazu noch approval_prompt=force gesetzt werden.
	# Siehe https://github.com/google/google-api-php-client/issues/263
	$client->setAccessType('offline');
	$client->setApprovalPrompt('force');

	/************************************************
	  Boilerplate auth management - see
	  user-example.php for details.
	 ************************************************/
	if (isset($_REQUEST['logout'])) {
		unset($_SESSION['access_token']);
	}
	if (isset($_GET['code'])) {
		$client->authenticate($_GET['code']);
		$_SESSION['access_token'] = $client->getAccessToken();
		$redirect = 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'];
		header('Location: ' . filter_var($redirect, FILTER_SANITIZE_URL));
	}

	if (isset($_SESSION['access_token']) && $_SESSION['access_token']) {
		$client->setAccessToken($_SESSION['access_token']);
	} else {
		$authUrl = $client->createAuthUrl();
	}

	# Falls schon ein RefreshToken gesetzt ist kann das Skript per Kommandozeile ausgeführt werden:
	if ($refresh_token) {

		# Damit hole ich mir bei JEDEM Aufruf einfach einen neuen Access-Token ;-)
		$client->refreshToken($refresh_token);

		# Außerdem die Variable $authUrl löschen, sonst wird weiter unten nur die URL zum Login angezeigt:
		unset($authUrl);
	}

	/************************************************
	  If we're signed in, retrieve contacts
	 ************************************************/
	if ($client->getAccessToken()) {

		if (DEBUGMODUS) echo "<hr><h1>Session-Variablen:</h1><pre>".print_r($_SESSION, true)."</pre>";

		$_SESSION['access_token'] = $client->getAccessToken();

		$access_token = json_decode($client->getAccessToken())->access_token;
		if (DEBUGMODUS) echo "<hr><h1>Client:</h1><pre>".print_r($client, true)."</pre>";
		if (DEBUGMODUS) echo "<hr><h1>Access-Token:</h1><pre>".print_r(json_decode($client->getAccessToken()), true)."</pre>";
		if (DEBUGMODUS) echo "<hr><h1>Weitere Infos zum Access-Token:</h1><pre>".print_r(json_decode(file_get_contents('https://www.googleapis.com/oauth2/v3/tokeninfo?access_token='.$access_token)), true)."</pre>";

		# Beim ersten Aufruf soll einfach nur der Refresh-Token zurückgegeben werden, der muss dann hier ins Skript eingetragen werden:
		if (!$refresh_token) {
			echo "Bitte den Code unterhalb der Linie in das Skript in die Variable $refresh_token eintragen:<hr>";
			echo "<pre>".json_decode($client->getAccessToken())->refresh_token."</pre>";
			exit();
		}

		//$url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results=3&alt=json&v=3.0&oauth_token='.$access_token;
		$url = 'https://www.google.com/m8/feeds/contacts/default/full?max-results=3000&alt=json&v=3.0&oauth_token='.$access_token;

		$response =  file_get_contents($url);

	}
	if (isset($authUrl)) {
		echo "<a class='login' href='" . $authUrl . "'>Zugriff auf Google Contacts autorisieren > KLICK!</a>";
	} else {
		if (DEBUGMODUS) echo "<hr><h1>Kontakte:</h1><pre>".print_r(json_decode($response), true)."</pre>";
		else echo $response;

	}

Dieses Skript muss auf den Raspberry Pi kopiert und so eingerichtet werden, dass es (vorübergehend) aus dem Netz erreichbar ist. Man sollte also irgend einen Webserver auf dem Pi laufen haben.

Außerdem braucht man den Google API PHP-Client, den man herunterladen und in sein Projekt einbinden muss. Könnte man auch per Composer machen, ist in diesem Fall aber vielleicht ein bisschen zu viel des Guten. Immerhin gibt es keine Abhängigkeiten von anderen Paketen.

Die Authentifizierung

Um dem Skript nun Zugriff auf die eigenen Daten zu gewähren, muss in der Google Developers Console ein neues Projekt angelegt werden: https://console.developers.google.com/ Das Projekt braucht einen beliebigen Namen. Außerdem muss man folgende APIs aktivieren:

  • Contacts API
  • Google Contacts CardDAV API
  • Google Partners API

Bin mir nicht wirklich sicher, ob man die „Google Contacts CardDAV API“ hier auch braucht, aber sie schadet nicht…

Anschließend brauchen wir Zugangsdaten, die wir (tataaa!) unter „Zugangsdaten“ generieren. Und zwar wählen wir „Client-ID für Webanwendung erstellen“. Wichtig ist an dieser Stelle, dass eine Weiterleitungs-URI gesetzt wird, die auf genau die URL zeigt, unter der unser PHP-Skript gerade im Netz erreichbar ist. Wenn wir die Client-ID erstellt haben bekommen wir unter anderem folgende Parameter: Eine „Client-ID“ und einen sog. „Clientschlüssel“. Diese beiden Werte müssen wir jetzt in unser PHP-Skript eintragen:


# Diese Daten hier erhält man nach der Registrierung eines Projekts in der Developers Console (https://console.developers.google.com/):
 # Client-ID:
 $client_id = 'HIER KOMMT DEINE CLIENT-ID REIN';
 # Clientschlüssel:
 $client_secret = 'HIER KOMMT DEIN CLIENT-SCHLÜSSEL REIN';

Wenn wir jetzt das PHP-Skript vom Web aus aufrufen, können wir den bekannten Authentifizierungsprozess starten: Das Skript leitet weiter zu einer Google-Authentifizierungsseite, dort sollte man dann alles gut finden und brav bestätigen, und am Ende wird man zurück zu unserem Skript geleitet. Das Skript ist jetzt authentifiziert, die Kontakte bei Google abzuholen. Allerdings verfällt der dazu vergeben Access Token schon innerhalb einer Stunde. Unpraktisch, wenn das Skript die Kontakte z.B. täglich ohne Zutun des Nutzers sichern soll. Daher wird von Google gleichzeitig ein sog. „Refresh Token“ angefordert. Mit Hilfe dieses Refresh Tokens kann das Skript bei jedem Aufruf einen neuen Access Token anfordern und anschließend mit diesem die Kontakte anfordern. Dazu muss man den Refresh Token irgendwo hinterlegen. Üblicherweise macht man das in einer Datenbank, der Einfachheit halber speichern wir ihn aber direkt im Skript. Das Skript zeigt den Refresh Token nach erfolgreicher Authentifizierung im Browser an, von dort kopieren wir ihn jetzt in unser Skript:


# Nach dem ersten Aufruf des Skripts per Webbrowser bekommt man einen RefreshToken,
# den man hier einträgt. Mit diesem kann dann bei jedem weiteren Aufruf
# ein neuer AccessToken geholt werden, mit dem dann wiederrum die Daten geholt werden:
$refresh_token = 'HIER KOMMT DEIN REFRESH-TOKEN REIN';

Die Ausführung

Ein Zugriff auf das Skript über das Internet ist nun nicht mehr nötig und sollte auch unterbunden werden. Denn ab jetzt soll das Skript nur noch per Kommandozeile oder Crontab ausgeführt werden, z.B. so:

php ~/skripte/backup-google-contacts.php | gzip > ~/backup/Google\ Kontakte/backup-google-contacts-`date +\%Y-\%m-\%d`.json.gz

Geschafft! Ich hoffe, der eine oder andere kann etwas mit dem Code-Chaos hier anfangen. Der Code sollte auf jeden Fall ein paar Anhaltspunkte für jemanden enthalten, der vor ähnlichen Problemen steht. Ich glaube, mir hätte das eine oder andere Code-Schnipsel weitergeholfen und mir die eine oder andere Nachtschicht erspart. Wobei, wäre vielleicht sogar schade gewesen, so ein Erfolgserlebnis ist ja auch was wert 😉

Raspberry Pi als Backupmaschine

Raspberry PiDer Raspberry Pi ist eine kleine Allzweckwaffe: Ein kompletter Rechner für ca. 35€, so groß wie eine Scheckkarte und mit einer sehr geringen Leistungsaufnahme (deren Kehrseite natürlich auch eine ziemlich geringe Leistung ist). Aber für manche Anwendungsfälle ist der Raspberry Pi genau das richtige Gerät. Ich kenne einige Leute, die sich vor lauter Begeisterung einen Raspberry Pi gekauft haben und nun nicht so richtig wissen, was sie mit dem schicken Teil nun eigentlich anfangen sollen. Mein Tipp: Als Backupmaschine verwenden.

Was der Raspberry Pi bei mir alles so backupt will ich in diesem Beitrag grob skizzieren. Voraussetzung ist ein fertig installiertes Raspian oder ein vergleichbares System, Grundkenntnisse im Umgang mit Linux (z.B. der crontab), dem Raspberry Pi an sich und zumindest eine gewisse Lernbereitschaft und Offenheit gegenüber z.B. Shell- und PHP-Skripten. Ich werde keine kompletten Anleitungen schreiben, eher so eine Gedankensammlung. Es muss und soll also weiter gebastelt und gegooglet werden!

Weiterlesen

Bulk Calendar – Mehrere Termine auf einmal bei Google Calendar eintragen

Ihr kennt das: Man will weder einen einzelnen Termin in seinen Google Calendar eintragen, noch einen Serientermin erstellen. Man hat beispielsweise eine Reihe an Seminaren mit verschiedenen Themen, irgendwelche Meetings an wechselnden Orten oder die Fussballtermine der Kinder gegen verschiedene Gegener. Man kann jetzt entweder auf die Details verzichten und doch einen Serientermine anlegen, oder aber man hat viel Handarbeit vor sich.

Genau diese Handarbeit kann man sich deutlich erleichtern, indem man die anzulegenden Termine in eine simple Google Tabelle einträgt und anschließend dort ein Skript startet, welches diese tabellarisch erfassten Daten in einen Kalender einträgt. Es ist schon etwas länger her, dass ich mich auf die Suche nach einem solchen Skript gemacht habe. Daher fehlt mir jetzt leider die Referenz auf das Original an dem ich anschließend noch ziemlich weitergebastelt habe.

Ich habe hier eine Kopie meiner Tabelle erstellt und freigegeben. Sieht in etwas so aus:

Bulk Calendar - Tabelle

Wenn du die Tabelle und das Skript verwenden willst erstellst du dir am besten erst mal eine Kopie der Tabelle (das Skript ist darin eingebettet):

Bulk Calendar - Kopie erstellen

Zuerst muss man logischerweise die gewünschten Daten in die Tabelle eintragen (Tabelle s.o.). Die grau markierten Zellen (Start- und Endzeitpunkt) dürfen nicht editiert werden. Wichtig ist, dass man die Formel in den grauen Spalten nur soweit nach unten kopiert, wie auch Daten in die Zeilen eingetragen sind:

Bulk Calendar - Formeln

Anschließend muss das Skript ausgeführt werden:

Bulk Calendar - Tools - Skriptmanager

Bulk Calendar - Skriptmanager Ausführen

Beim Ausführen des Skript gibt es diverse Kontroll-Dialoge, die man einfach im Skript nach Belieben auskommentieren kann.

Der Vollständigkeit halber hier noch der Code des Skripts. Wie immer quick’n’dirty, tut es aber für mich. Falls jemand damit weiter bastelt bin ich offen für Anregungen.


// Custom Menu:
// see https://developers.google.com/apps-script/guides/bound#custom_menus_dialogs_and_sidebars
function onOpen() {
  
//  var cals = CalendarApp.getAllOwnedCalendars();
//  Browser.msgBox('Es gibt genau %s Kalender zu Auswahl:', CalendarApp.getAllOwnedCalendars().length);
//  for (var c=0; c < cals.length; c++) {
//    ui.createMenu('Termine eintragen')
//      .addItem(cals[c].getName(), 'TermineInCalendarEintragen')
//      .addToUi();
//  }  
  
  var ui = SpreadsheetApp.getUi();
  ui.createMenu('Termine eintragen')
      .addItem('Go!', 'TermineInCalendarEintragen')
      .addToUi();

}





function TermineInCalendarEintragen() {
 
  // Default-Kalender auswählen:
  //var cal = CalendarApp.getDefaultCalendar();
 
  // Besser: Auswahl an Kalendern anbieten.
  // Ansatz: Alle möglichen Kalender auflisten, per Eingabefeld den gewünschten eingeben lassen:
  var kalenderauswahl = 'In welchen Kalender sollen die Termine eingetragen werden?\\n\\n';
  // Alle eigenen Kalender abrufen:
  var cals = CalendarApp.getAllOwnedCalendars();
  
  Logger.log('Es gibt genau %s Kalender zu Auswahl:', cals.length);
  
  // Kalender durchgehen und Ausgabenachricht zusammenbasteln:
  //for (c in cals) {
  for (var c=0; c < cals.length; c++) {
    Logger.log("ID: " + cals[c].getName());
    kalenderauswahl = kalenderauswahl + '- ' + cals[c].getName() + '\\n';
  }
  kalenderauswahl = kalenderauswahl + '\\nBitte Kalendername EXAKT eingeben:';
  var kalendername = Browser.inputBox(kalenderauswahl)
  Logger.log("Kalender: " + kalendername);
  
  // Jetzt den Kalender mit dem übergebenen Namen auswählen:
  cal = CalendarApp.getCalendarsByName(kalendername);
 
  
  // Falls ein (brauchbarer) Kalendername eingegeben wurde:
  if (kalendername && cal.length > 0) {
    Browser.msgBox('Kalender "' + cal[0].getName()  + '" gewählt.')
    
    // Jetzt werden die Daten aus der Tabelle gezogen:
    var sheet = SpreadsheetApp.getActiveSheet();
    var startRow = 2;
    var numRows = SpreadsheetApp.getActiveSheet().getLastRow()-1;  // -1, weil die Kopfzeile keine Daten enthält
    Browser.msgBox('Es sollen ' + numRows + ' Termine eingetragen werden.')
    // Datenbereich auswählen:
    var dataRange = sheet.getRange(startRow, 1, numRows, 8);
    // Daraus Daten in Variable schreiben:
    var data = dataRange.getValues();
    // Jetzt jede Zeile durchgehen:
    for (d in data) {
      // Daten aus dieser Zeile wählen:
      var row = data[d];
      
      // Jetzt einzelne Felder davon:
      var title  = row[0];
      var tstart = row[4];
      var tstop  = row[5];
      var loc    = row[6];
      var desc   = row[7];
      
      // Jetzt wird der Termin eingetragen:
      // https://developers.google.com/apps-script/class_calendarapp?hl=de#createEvent
      cal[0].createEvent(title, tstart, tstop, {description:desc,location:loc});
      
      // ... und noch eine Meldung ausgegeben. Denn manchmal steigt das Skript aus, und so weiß man, wie weit es war.
      Browser.msgBox( parseInt(d)+1 + '. Termin in Kalender "' + kalendername + '" eingetragen:\\n\\n' + title + '\\n\\n' +  tstart + '\\n\\n' + desc )
    }
    // Am Ende nochmal Meldung, dann ist klar, dass alles durchgelaufen ist:
    Browser.msgBox('Fertig!');
  }
  else {
    Browser.msgBox('Kein Kalender ausgewählt oder Kalender falsch angegeben...')
  }
}


Kontakteverwaltung: Perfektionistisch bis zwanghaft

Ich gebe zu, bei meiner Kontaktverwaltung bin ich durchaus perfektionistisch bis zwanghaft. Jahre lang habe ich Outlook dafür herangezogen und war auch mit der Synchronisation mit meinen ersten mobilen Endgeräten sehr zufrieden. Mit dem Umstieg auf Android bin ich dann schweren Herzens auch mit meinen Kontakten zu Google Contacts umgezogen. Mit welchem Programm ich das gemacht habe weiß ich leider nicht mehr, die Suche hat damals etwas gedauert, weil es mir wichtig war, alle Kontaktbilder (und das waren viele!) beim Umzug nicht zu verlieren. Hat dann auch alles soweit geklappt, einzig die Darstellung der Namen war etwas unschön. Dazu muss man wissen, dass es in Kontaktverwaltungen verschiedenste Namensfelder gibt, einfach Vor- und Nachname wäre da zu simpel. Bei meinem Umzug wurden die Felder für Vor- und Nachnamen korrekt belegt, zusätzlich wurde aber der angezeigte Name auf „Nachname, Vorname“ gesetzt. Das ist eigentlich erst mal egal, kann aber unschön werden, wenn man dann andere Tools mit seinen Google-Kontakten synchronisiert. Ich nutze bspw. den automatischen Datenabgleich mit meiner Fritzbox, der dafür sorgt, dass bei allen Nummern, die ich in meinen Kontakten habe, auf dem Festnetztelefon der Name des Anrufers angezeigt wird, auch wenn dieser gar nicht im Festnetztelefon gespeichert ist. Und ich nutze das Thunderbird-Add-On „Google Contacts“, um im Mailclient meines Vertrauens immer die Kontakte aktuell zu haben. Und dort ergibt sich dann z.B. das „Problem“, dass eine Mail dann nicht an ‚Vorname Nachname <mail@dres.se>‘ gesendet wird, sondern an ‚“Nachname, Vorname“ <mail@dres.se>‘. Ist jetzt nicht weiter schlimm, erzeugt aber beim Kontaktperfektionisten ein gewisses Unbehagen.

In Outlook hatte ich mir mal ein kleines VBA-Schnipsel erstellt, um alle Kontakte durchzugehen und Felder nach meinen Änderungswünschen zu modifizieren. Sowas wollte ich jetzt auch für meine Google-Kontakte haben und habe mich mal hingesetzt, und eine kleine PHP-Lösung zusammengestrickt. Der Großteil des Codes ist von irgendwo zusammengeklaut und eben für meine Bedürfnisse angepasst, benutzt wird außerdem das Zend Framework. Ich habe damit jetzt einfach mal alle „Nachname, Vorname“ in „Vorname Nachname“ ändern lassen, es lässt sich aber auch leicht an andere „Problemfälle“ anpassen. Viel Spaß.
Weiterlesen

Rechts-Äuger

Bildungslücke! Ich wusste zwar, dass ich zwei Augen habe, und dass ich – da beide ganz ordentlich funktionieren – damit auch räumlich sehen kann. In meinem symmetrischen Denken bin ich davon ausgegangen, dass es daher auch gut wäre, wenn beide Augen gleich viel zu meinem visuellen Eindruck beitragen würden. Pustekuchen.

Im Zusammenhang mit dem Projekt Google Glass bin ich über ein Video und darin zum ersten Mal über den Begriff der „Augendominanz“ gestolpert. Augendominanz bedeutet grob gesagt, dass das Gehirn bei der Berechnung des visuellen Eindrucks die Informationen des einen Auges denen des anderen Auges vorzieht. Um festzustellen, welches Auge bei dir dominant ist, kannst du folgenden recht einfachen Test durchführen:

Zur Feststellung der Augendominanz gibt es verschiedene Verfahren. Bei vielen wird die Versuchsperson darum gebeten ein näheres (beispielsweise den eigenen Daumen bei ausgestreckten Arm) und ein ferneres Objekt (beispielsweise ein senkrechter Strich an einer Wand) aufeinander abzustimmen (beispielsweise den Daumen so zu positionieren, dass er auf dem Strich liegt) und das nähere Objekt zu fixieren. Nun wird die Versuchsperson gebeten die Augen abwechselnd zu schließen. Schließt sie das nicht dominante Auge verändert sich die wahrgenommene Position der Objekte zueinander nicht, da die binokuläre Wahrnehmung ohnehin auf das dominante Auge abgestimmt ist. Schließt die Versuchsperson jedoch das dominante Auge verändert sich die Position der Objekte zueinander, da die visuelle Information des nicht dominanten Auges nicht mehr an die des dominanten Auges angepasst werden kann.

via de.wikipedia.org

Ergebnis: Ich bin Rechts-Äuger.

Geduld aber ist euch not

via ehrensenf.de

„Geduld aber ist euch not“ steht im Hebräerbrief. Das kann man ruhig mal aus dem Zusammenhang reißen und einfach so allgemeingültig dahersagen, kein Problem. Denn wer sich schon einmal wie das Poker Face rechts im Film gefühlt hat oder sich gar in schöner Regelmäßigkeit so fühlt, der weiß um die dringende Notwenigkeit von Geduld. Andernfalls endet es wie im Film.

Google Beatbox

via spreeblick.com

Google kann bekanntlich ja nicht nur Texte übersetzen, sondern diese auch vorlesen. Jemand hatte die glorreiche Idee, mit mäßig sinnvollen Texten und dieser Vorlesefunktion eine Beatbox zu realisieren. Ausprobieren! Wie man aber darauf kommt, dass bei der schwedischen Übersetzung dieses „Textes“ bei „ZK“ dann „Santa Claus“ gesagt wird ist mir ein Rätsel…

Ein ganz Fleißiger hat sogar noch ein Bookmarklet gebastelt, um die Sounds direkt bei Google Translate einfügen zu können. Dem Spieltrieb sind wiedermal keine Grenzen gesetzt.

O tempora, o mores!

Numerus me sociare Latinis est quintum Latinum primum a quinto gradu, quinque mi postrema undecimi alia quinque. Nimirum hanc linguam interesting, excitat et offert magistro pulcherrima.At tu facere aliquid. Fuit. Nunc, Google officium. Licet scire nihil hic nisi videatur. Bene et revera etiam ea commodo …

via translate.google.com

Die Zahl, die ich mit Latein verbinde, ist die Fünf: Meine erste Latein-Note in der fünften Klasse war eine Fünf, meine letzte am Ende der elften wieder eine Fünf. Keine Frage, diese Sprache ist interessant, spannend und bietet die seltsamsten Lehrer. Aber man musste etwas dafür tun. Musste. Denn jetzt übernimmt Google den Job. Lernt man zwar nix dabei, aber egal. Naja, und so richtig funktioniert das ja auch nicht…

PS: Wenn man diesen Text de->lat->de übersetzten lässt, dann kommt das dabei raus:

Die Zahl der für mich lateinischen Sprache und Latein auf den ersten in der fünften fünften Grades, der fünf der letzten der elften, meine fünf anderen zu vereinigen. Natürlich, die Sprache zu einigen interessanten, spannenden und bietet Master der schönsten Reichtum. Aber wissen Sie, etwas zu tun. Es gab a. Nun, das Büro von Google verarbeiten. Obwohl hier nichts zu wissen, wenn sie gesehen werden. Wir taten gut daran, auch diese zu passen in der Tat machen …

via translate.google.com