I used this other question as an example, and it appears like it works, but the contact's photo doesn't actually change, and no error is returned.
I'm able to get the contact's current photo and delete the contact's photo just fine.
const accessToken = ScriptApp.getOAuthToken();
const id = '4c18faa28828aa3f';
const url = '-URL Omitted-';
const blob = UrlFetchApp.fetch(url).getBlob();
const data = Utilities.base64EncodeWebSafe(blob.getBytes());
const response = UrlFetchApp.fetch(`https://www.google.com/m8/feeds/photos/media/me/${id}`, {
method: 'put',
contentType: 'image/jpeg',
payload: data,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const content = response.getContentText();
console.log(content);
<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom'>
<id>http://www.google.com/m8/feeds/photos/media/-OMITTED-%40gmail.com/4c18faa28828aa3f</id>
<updated>2020-10-12T22:10:01.271Z</updated>
<link rel='self' type='application/atom+xml'
href='https://www.google.com/m8/feeds/photos/media/-OMITTED-%40gmail.com/4c18faa28828aa3f'/>
<link rel='edit' type='application/atom+xml'
href='https://www.google.com/m8/feeds/photos/media/-OMITTED-%40gmail.com/4c18faa28828aa3f/1B2M2Y8AsgTpgAmY7PhCfg'/>
</entry>
I figured it out. I was encoding the image blob bytes for the payload for some reason. I should have just passed the blob as the payload like this.
const accessToken = ScriptApp.getOAuthToken();
const id = '4c18faa28828aa3f';
const url = '-URL Omitted-';
const blob = UrlFetchApp.fetch(url).getBlob();
const response = UrlFetchApp.fetch(`https://www.google.com/m8/feeds/photos/media/me/${id}`, {
method: 'put',
contentType: 'image/jpeg',
payload: blob,
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const content = response.getContentText();
console.log(content);