Nu blijkt dat Facebook best veel data van mensen opslaat zijn er nogal wat mensen die met Facebook willen stoppen. Je kunt een verzoek doen om je data beschikbaar te stellen, zodat je onder andere je foto's kunt downloaden. Klaar. Of toch niet. Want Jelmer Luimstra kwam met een #durftevragen post op Facebook, hoe zit het met foto's waar je in getagd bent? Die zitten daar niet in. Logisch op zich, want het zijn niet "jouw" foto's, maar wel begrijpelijk dat je die ook zou willen hebben.
Die mogelijkheid heb je wel, maar dan moet je met de Graph API van Facebook gaan werken. Voor de meeste gebruikers is deze term al abracadabra, dus een groot deel van de gebruikers zal dat niet lukken. Tijd om mijn superheldencape om te doen en te kijken of ik hier een makkelijke oplossing voor kan maken.
Stap 1, maak een app. Ik ben naar https://developers.facebook.com gegaan en heb daar een app aangemaakt. Ik heb dat al eerder gedaan, want ik deel mijn producten van www.prijs-bewust.nl, mijn blog-posts van www.durkotheek.nl en natuurlijk de nieuwsberichten op www.wergea.com. Je kunt er dan een product aan toevoegen. Mijn andere apps werken "onder water" met het aansturen van code, maar nu moet je iets hebben waarbij een Facebooker kan inloggen/autoriseren en vervolgens een overzicht krijgt van zijn/haar foto's. Ik heb eerst maar gekozen voor Facebook Login. In de volgende stap gekozen voor www. Hierna de rest van de stappen doorgelopen. In eerste instantie is het een blanco pagina met alleen een inloggen-knop. Bij die knop moet ik wel een paar extra rechten toevoegen, namelijk user_photo, user_tagged_places en voor de zekerheid heb ik ook nog user_videos toegevoegd, ik ben benieuwd of we die ook kunnen downloaden. Nou ja, ik kan ze in ieder geval zichtbaar maken.
Mijn initiële pagina ziet er zo uit:
<html>
<head>
<title>Download je Facebook foto's</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : 'xxxxxxxxxx',
cookie : true,
xfbml : true,
version : 'v2.11'
});
FB.AppEvents.logPageView();
checkLoginState();
};(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "https://connect.facebook.net/en_US/sdk.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<script language="javascript">
function statusChangeCallback(response) {
if (response.status == 'not_authorized') {
FB.login();
return;
}
}function checkLoginState(){
FB.getLoginStatus(function(response) {
statusChangeCallback(response);
});
}
</script>
<fb:login-button scope="public_profile,email,user_photo,user_tagged_places,user_videos" onlogin="checkLoginState();"></fb:login-button>
</body>
</html>
En ja hoor, dit werkt (natuurlijk) niet. Want die extra rechten op foto's, die heb je niet beschikbaar. Facebook heeft het ook niet zo gemaakt dat het extra gemeld wordt in het inlogscherm. Ik heb vervolgens een andere app geprobeerd, waarbij ik de Open Graph api als product kon toevoegen (dat wil ik ook gebruiken). Maar ook daar is het niet aanwezig. Je kunt wel een verzoek indienen bij Facebook, maar dan moet je ook nog een screencast van "hoe je het gaat gebruiken" indienen. Man, man, man...
We gaan het dus op een andere manier doen. Want als je in de Explorer van de Development-omgeving van Facebook zit, dan kun je (als je de juiste keuzes maakt) die gegevens wel opvragen. Mijn inloggen-knop laat ik dus staan, om te zorgen dat de persoon op mijn pagina ingelogd is. Daarna verschijnt een link naar de Explorer-pagina. Die open je in een nieuwe tab, vervolgens toon ik de stappen die je moet volgen en waarna je vervolgens alleen maar het Access Token hoeft te kopiëren. Je kunt vervolgens ter controle 1 foto opvragen om te zien of het werkt.
Als je hierna de rest van de foto's wilt downloaden, dan is het enige wat je hoeft te doen mijn Facebookpagina te liken. Want het heeft me toch ongeveer 2 dagen gekost om het werkend te krijgen, dan mag er wel een kleine tegenprestatie tegenover staan. Daarna kun je per 20 stuks de foto's opvragen en die 20 foto's die je ziet in 1 zip-bestand downloaden.
Mijn initiële opzet was in PHP uitgevoerd, maar omdat ik een zip-bestand wil kunnen genereren heb ik het overgezet naar .NET. Je hebt daar namelijk de System.IO.Compression-namespace waarmee je kunt "zippen".
Dat was nog wel even pielen, want het opslaan in een MemoryStream en vervolgens naar je browser sturen, daar miste een klein stukje data, waardoor het zip-bestand corrupt was. Door de "leaveOpen"-parameter op True te zetten gaat het nu wel goed.
List<string> photos = new List<string>{ "http://.....", "https://....." };
....
if (photos != null)
{
MemoryStream ms = new System.IO.MemoryStream();
using (ZipArchive za = new ZipArchive(ms, ZipArchiveMode.Create, true))
{foreach (var s in photos)
{
string filename = Path.GetFileName(s);
if (filename.Contains("?"))
{
filename = filename.Substring(0, filename.IndexOf('?'));
}
var entry = za.CreateEntry(filename);
Stream entryStream = entry.Open();
using (WebClient wc = new WebClient())
{
byte[] data = wc.DownloadData(s);
entryStream.Write(data, 0, data.Length);
entryStream.Close();
}
}
}
ms.Flush();
ms.Position = 0;
Response.ClearContent();
Response.ClearHeaders();
Response.AppendHeader("content-disposition", "attachment; filename=facebookfotos_" + DateTime.Now.ToString("yyyyMMddhhmmss") + ".zip");
Response.BinaryWrite(ms.ToArray());
ms.Close();
}
else
{
Response.Write("Er zijn geen bestanden te downloaden!");
}
Met NewtonSoft.Json kun je de gegevens die de Facebook API je teruggeeft makkelijk filteren.
Hieronder een aantal delen uit mijn code;
public class FBListResult
{
public FBListItem[] data;
public FBPaging paging;
}public class FBListItem
{
public string id;
}public class FBItemResult
{
public FBImage[] images;}
public class FBImage
{
public string source;
}
...
internal void FetchPhotos(string url, bool demoMode)
{
using (WebClient wc = new WebClient())
{
string jsonData = UTF8Encoding.UTF8.GetString(wc.DownloadData(url));
Newtonsoft.Json.JsonSerializer serializer = new Newtonsoft.Json.JsonSerializer();
StringReader stringReader = new StringReader(jsonData);
Newtonsoft.Json.JsonTextReader jsonTextReader = new Newtonsoft.Json.JsonTextReader(stringReader);
FBListResult fbResult = serializer.Deserialize<FBListResult>(jsonTextReader);
foreach (var item in fbResult.data)
{
url = string.Format("https://graph.facebook.com/v2.12/{0}?fields=images&access_token={1}", item.id, _accessToken);
string jsonItemData = UTF8Encoding.UTF8.GetString(wc.DownloadData(url));
StringReader stringReaderItem = new StringReader(jsonItemData);
Newtonsoft.Json.JsonTextReader jsonItemTextReader = new Newtonsoft.Json.JsonTextReader(stringReaderItem);
FBItemResult fbItemResult = serializer.Deserialize<FBItemResult>(jsonItemTextReader);
if (fbItemResult.images.Count() > 0)
{
_photos.Add(fbItemResult.images.First().source);
}}
_next = null;
_previous = null;
if (demoMode == false)
{
if (string.IsNullOrEmpty(fbResult.paging.next) == false)
{
_next = fbResult.paging.next;
}
if (string.IsNullOrEmpty(fbResult.paging.previous) == false)
{
_previous = fbResult.paging.previous;
}
}
}
}
Wil je ook jouw foto's downloaden? De code draait op deze site: https://help.prijs-bewust.nl/