使用c#連接google photos api實作
谷歌在2018年I/O大會宣布開放了photos api給開發者使用,終於!
非常需要這支api的傻露研究了一下~。
1、前言
傻露一直是picasa的使用者,
由於google在2016年宣布不再更新picasa,建議使用者使用自家的google photos,
並在2018年3月關閉picasa軟體上傳至google photos的通道後,
使用google提供的picasa api無法取得完整的google photos資料,
這讓傻露一直相當頭痛,終於在今年5月看到google釋出photos api的消息,
就來花點時間嘗試看看如何取得自己帳戶內的照片資料啦。
2、事前準備
開發者請先至google替你的應用程式申請clientid、clientsecret。
申請網址
傻露使用之開發工具為VS2013、以MVC+C#實作讀取的部分。
3、實作
1.新增mvc專案
2.nuget下載JSON.NET套件
3.加入命名空間
using Newtonsoft.Json;
using System.Net;
using System.Text;
using System.IO;
4.定義向google申請到的clientid、clientsecret,以及在申請頁面中填入的callback url
public class HomeController : Controller
{
private string strClientID = "{CLIENT_ID}";
private string strClientSecret = "{CLIENT_SECRET}";
private string strRedirectUrl = "{CALLBACK_URL}";
}
5.連結至google OAuth
public ActionResult Index()
{
string Url = "https://accounts.google.com/o/oauth2/auth?scope={0}&redirect_uri={1}&response_type={2}&client_id={3}&state={4}";
//scope:欲存取的scope,這裡只作讀取:所有相簿=分享+未分享
string scope = "https://www.googleapis.com/auth/photoslibrary.readonly";
string redirect_uri = strRedirectUrl;
string response_type = "code";
string state = "";
Response.Redirect(string.Format(Url, scope, redirect_uri, response_type, strClientID, state));
return null;
}
(2018.8月更新)
若使用refresh token,Url字串須加上access_type的參數,值為變數access_type。
更改以及加入代碼如下:
string Url = "https://accounts.google.com/o/oauth2/auth?scope={0}&redirect_uri={1}&response_type={2}&client_id={3}&state={4}&access_type={5}";
string access_type="offline"; //default=online,若要在使用者離線時亦能存取,須將此值設定為offline。
Response.Redirect(string.Format(Url, scope, redirect_uri, response_type, strClientID, state, access_type));
關於refresh token的用途,詳細可參考Google Identity Platform文件
文件內表示,發給的access token具有時效性,時效一過,重新向谷歌申請時又會向使用者要求重新作Oauth認證。
因此設計refresh token此一機制,在使用者離線時,使用者不需再作Oauth認證,透過refresh token重新取得access token並重新取得資料。
6.在使用者登入並同意存取相簿內資料後,api會回傳一個token data(json格式)。
因此在這裡先定義一個TokenData物件接取資料。
public class TokenData
{
/// <summary>
/// access_token
/// </summary>
public string access_token { get; set; }
/// <summary>
/// refresh_token
/// </summary>
public string refresh_token { get; set; }
/// <summary>
/// expires_in
/// </summary>
public string expires_in { get; set; }
/// <summary>
/// token_type
/// </summary>
public string token_type { get; set; }
}
7.接收token data
public ActionResult Callback(string Code)
{
// 沒有接收到參數
if (string.IsNullOrEmpty(Code))
return Content("沒有收到 Code");
string Url = "https://accounts.google.com/o/oauth2/token";
string grant_type = "authorization_code";
string redirect_uri = strRedirectUrl;
string data = "code={0}&client_id={1}&client_secret={2}&redirect_uri={3}&grant_type={4}";
//get token
HttpWebRequest request = HttpWebRequest.Create(Url) as HttpWebRequest;
string result = null;
request.Method = "POST"; // 方法
request.KeepAlive = true; // 是否保持連線
request.ContentType = "application/x-www-form-urlencoded";
string param = string.Format(data, Code, strClientID, strClientSecret, redirect_uri, grant_type);
byte[] bs = Encoding.ASCII.GetBytes(param);
using (Stream reqStream = request.GetRequestStream())
{
reqStream.Write(bs, 0, bs.Length);
}
using (WebResponse response = request.GetResponse())
{
StreamReader sr = new StreamReader(response.GetResponseStream());
result = sr.ReadToEnd();
sr.Close();
}
TokenData tokenData = JsonConvert.DeserializeObject<TokenData>(result);
Session["token"] = tokenData.access_token; //只取access_token
return RedirectToAction("CallAPI");
}
(2018.8月更新)
如果在上面第5.步驟中設定access_type=“offline”,於本步驟程式碼執行後,取得的tokenData內即會給予refresh_token之資料
若使用refresh token,更改及新增代碼如下:
string grant_type = "refresh_token";
string strRefreshToken = "{REFRESH_TOKEN}";
string data = "client_id={0}&client_secret={1}&refresh_token={2}&grant_type={3}";
string param = string.Format(data, strClientID, strClientSecret, strRefreshToken, grant_type);
8.call api
public ActionResult CallAPI()
{
//--------1.取得相簿清單-----------
if (Session["token"] == null)
return Content("請先取得授權!");
string token = Session["token"] as string;
// 取得album的 API 網址
string Url = "https://photoslibrary.googleapis.com/v1/albums?access_token=" + token + "&pageSize=50"; //pageSize預設=20,上限=50,不加此參數即會只回傳20筆資料
HttpWebRequest request = HttpWebRequest.Create(Url) as HttpWebRequest;
string result = null; //api回傳的結果
request.Method = "GET"; // 方法
request.KeepAlive = true; // 是否保持連線
using (WebResponse response = request.GetResponse())
{
StreamReader sr = new StreamReader(response.GetResponseStream());
result = sr.ReadToEnd();
sr.Close();
}
Response.Write(result);
}
執行之result列印出來內容大致如下:
{
"albums": [
{
"id": "AGj1epU-......",
"title": "相簿1",
"productUrl": "https://photos.google.com/lr/album/AGj1epU-......",
"totalMediaItems": "96",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/AJZSZux......"
},
{
"id": "AGj1epUO......",
"title": "相簿2",
"productUrl": "https://photos.google.com/lr/album/AGj1epUO......",
"totalMediaItems": "54",
"coverPhotoBaseUrl": "https://lh3.googleusercontent.com/photos-library/AJZSZux......"
},
...
],
"nextPageToken": "AH_uQ438......"
}
如果有”nextPageToken”此欄位,就代表後面還有資料,要以此token加在api網址後方繼續查詢,可獲得下一組資料。
即把Url改為:
string nextPageToken = "{nextPageToken}"; //取自上方api回傳之資料
Url = "https://photoslibrary.googleapis.com/v1/albums?access_token=" + token + "&pageToken=" + nextPageToken + "&pageSize=50";
欲取得單一相簿內所有照片資料,需使用Post方式連接api:
public ActionResult CallAPI()
{
//接續上方CallAPI()之內容
string contentUrl = "https://photoslibrary.googleapis.com/v1/mediaItems:search?access_token=" + token + "&pageSize=100"; //pageSize:預設50,上限100
string albumid = "AGj1epV5o......"; //單一相簿的id,即上方result內"albums"之"id"
request = HttpWebRequest.Create(contentUrl) as HttpWebRequest;
string resultContent = null; //api回傳的結果
request.Method = "POST"; // 方法
request.KeepAlive = true; // 是否保持連線
request.ContentType = "application/json; charset=utf-8";
request.Headers.Add("Authorization", "Bearer " + token);
string requestBody = "{\"pageSize\": \"100\",\"albumId\": \""+albumid+"\"}"; //google要求的格式為{"pageSize":"100","albumId": "ALBUM_ID"}
Stream ws = request.GetRequestStream();
using (var streamWriter = new StreamWriter(ws, new UTF8Encoding(false)))
{
streamWriter.Write(requestBody);
streamWriter.Flush();
streamWriter.Close();
}
using (WebResponse response = request.GetResponse())
{
StreamReader sr = new StreamReader(response.GetResponseStream());
resultContent = sr.ReadToEnd();
sr.Close();
}
Response.Write(resultContent);
}
執行之resultContent列印出來內容大致如下:
{
"mediaItems": [
{
"id": "AGj1epUz......",
"description": "description",
"productUrl": "https://photos.google.com/lr/album/AGj1epUu......",
"baseUrl": "https://lh3.googleusercontent.com/photos-library/AJZSZuyU......",
"mimeType": "image/jpeg",
"mediaMetadata": {
"creationTime": "2014-09-06T13:53:36Z",
"width": "800",
"height": "450",
"photo": {}
}
},
]
}
與上面抓取所有相簿資料相同的,如果有”nextPageToken”此欄位,就代表後面還有資料,要獲得下一組資料就必須以此token加在api網址後方繼續查詢。
總結
以上程式碼示範了讀取相簿資料,其他還有新增、更新等等的部分,原則是差不多的,參考文件實作應該不會太難。
原先傻露是為了要以api取得google相簿資料以製作自己的相簿藝廊(photo album / gallery),
畢竟目前google相簿
1.相簿排序無法變更為自己喜愛的方式(按照相簿名稱排序)
2.只有單一相簿連結分享,無UI可看到所有公開的相簿
而在傻露實際try過google photos api後發現:
1.以token取得的相簿、照片資訊,包含id、baseurl(圖床),依不同token均不同
每次以不同access token取得的相簿、照片資訊,包含id、baseurl(圖床)均不同(2018.8月更新)
2.若是透由【google相簿】上傳的資料,上述1.取得的資料具有時效性,時效一過,沒有登入google帳戶該張圖片是會產生破圖,無法讀取的(時效大約1日內吧)
雖然不清楚google這樣做的目的是甚麼,不過傻露大概猜測了一下,
除了不鼓勵使用者把google的相簿當作圖床來使用、不希望使用者可以像picasa那樣容易取得相簿的json資料、也不希望開發者儲存使用者的資料吧,
所以雖然乍看之下可以透過google photos api取得自己的所有相簿資料,但就製作公開的gallery而言有困難,
所以也只好放棄google photos api這條管道了,
只好安慰自己,當作一個程式練習,也多了解google api如何運作囉。
另外感謝網友Allen提醒access token與refresh token之不同,傻露也據此調整了內文。
若有未盡詳細或錯誤之處,也歡迎各位指正,謝謝。^_^”
參考資料
Google Photos API
Google Identity Platform
【oAuth 2.0 實作系列】ASP.Net MVC 實作使用 oAuth 2.0 連接 Google API
Android アプリで Google Photos APIs を使ってみた!!