Gmail api scope & format mismatch - java

I'm trying to write tiny gmail client for android as training.
I took gmail api guide sample from https://developers.google.com/gmail/api/quickstart/android modified it a little to get messages with headers & body by threads. I set scopes to GmailScopes.Gmail_modify and edited main request function as this:
private List<String> getDataFromApi() throws IOException {
// Get the labels in the user's account.
String user = "me";
List<String> labels = new ArrayList<String>();
ListLabelsResponse listResponse =
mService.users().labels().list(user).execute();
ListThreadsResponse listThreads = null;
try {
listThreads = mService.users().threads().list(user).execute();
} catch (IOException ioex){
Log.e(LOG_TAG, "First: " + ioex.toString());
}
for (Thread thread : listThreads.getThreads()) {
try {
thread = mService.users().threads().get(user, thread.getId()).setFormat("full").execute();
} catch (IOException ioex){
Log.e(LOG_TAG, "Second: " + ioex.toString());
}
for(Message message : thread.getMessages()){
labels.add(message.getId());
}
}
return labels;
}
But I always get
Second: GoogleJsonResponseException: 403 Forbidden {
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Metadata scope doesn't allow format FULL",
"reason" : "forbidden"
} ],
"message" : "Metadata scope doesn't allow format FULL"
}
I tried different scopes configurations but seems like service scope is always set to GmailScopes.GMAIL_METADATA

This is exactly my problem these day when playing with Google APIs Explorer. And here is what I did to solve it:
Go to https://security.google.com/settings/security/permissions
Choose the app you are playing with, mine is Google APIs Explorer
Click Remove > OK
Next time, just request exactly which permissions you need.
Hope it help :)

you should remove the "metadata" scope.
check app permissions to make sure you have only these 3 scopes:
https://mail.google.com/
gmail.modify
readonly
, or else remove the permissions and add them again.

After getting permissions to device Contacts you have to approve chosen copes. So first time I approved metadata scope. Next times when I needed to approve readonly scope, there was no window to do it. So you need to delete scopes permissions from google account and reinstall app.

got the same error when a service account which i was using had only these scopes ( Security > API Controls > Domain-wide Delegation ):
https://www.googleapis.com/auth/gmail.readonly
https://www.googleapis.com/auth/gmail.metadata
and addition of
https://mail.google.com/
solved the issue

Related

Downloading attachments from unseen messages

I work on university project in java. I have to download attachments from new emails using GMAIL API.
I successfully connected to gmail account using OAuth 2.0 authorization.
private static final List<String> SCOPES = Collections.singletonList(GmailScopes.GMAIL_READONLY);
I tried to get unseen mails using
ListMessagesResponse listMessageResponse = service.users().messages().list(user).setQ("is:unseen").execute();
listMessageResponse is not null but when I call method .getResultSizeEstimate() it returns 0
also I tried to convert listMessageResponse to List < Message > (I guess this is more usable) using
List<Message> list = listMessageResponse.getMessages();
But list launches NullPointerException
Then tried to get each attachment with
for(Message m : list) {
List<MessagePart> part = m.getPayload().getParts();
for(MessagePart p: part) {
if(p.getFilename()!=null && p.getFilename().length()>0) {
System.out.println(p.getFilename()); // Just to check attachment filename
}
}
}
Is my approach correct (if not how to fix it) and how should I download those attachments.
EDIT 1:
Fixed q parameter, I mistakenly wrote is:unseen instead of is:unread.
Now app reaches unread mails successfully.
(For example there was two unread mails and both successfully reached, I can get theirs IDs easy).
Now this part trows NullPointerException
List<MessagePart> part = m.getPayload().getParts();
Both messages have attachments and m is not null (I get ID with .getID())
Any ideas how to overcome this and download attachment?
EDIT 2:
Attachments Downloading part
for(MessagePart p : parts) {
if ((p.getFilename() != null && p.getFilename().length() > 0)) {
String filename = p.getFilename();
String attId = p.getBody().getAttachmentId();
MessagePartBody attachPart;
FileOutputStream fileOutFile = null;
try {
attachPart = service.users().messages().attachments().get("me", p.getPartId(), attId).execute();
byte[] fileByteArray = Base64.decodeBase64(attachPart.getData());
fileOutFile = new FileOutputStream(filename); // Or any other dir
fileOutFile.write(fileByteArray);
fileOutFile.close();
}catch (IOException e) {
System.out.println("IO Exception processing attachment: " + filename);
} finally {
if (fileOutFile != null) {
try {
fileOutFile.close();
} catch (IOException e) {
// probably doesn't matter
}
}
}
}
}
Downloading working like charm, tested app with different type of emails.
Only thing left is to change label of unread message (that was reached by app) to read. Any tips how to do it?
And one tiny question:
I want this app to fetch mails on every 10 minutes using TimerTask abstract class. Is there need for manual "closing" of connection with gmail or that's done automatically after run() method iteration ends?
#Override
public void run(){
// Some fancy code
service.close(); // Something like that if even exists
}
I don't think ListMessagesResponse ever becomes null. Even if there are no messages that match your query, at least resultSizeEstimate will get populated in the resulting response: see Users.messages: list > Response.
I think you are using the correct approach, just that there is no message that matches your query. Actually, I never saw is:unseen before. Did you mean is:unread instead?
Update:
When using Users.messages: list only the id and the threadId of each message is populated, so you cannot access the message payload. In order to get the full message resource, you have to use Users.messages: get instead, as you can see in the referenced link:
Note that each message resource contains only an id and a threadId. Additional message details can be fetched using the messages.get method.
So in this case, after getting the list of messages, you have to iterate through the list, and do the following for each message in the list:
Get the message id via m.getId().
Once you have retrieved the message id, use it to call Gmail.Users.Messages.Get and get the full message resource. The retrieved message should have all fields populated, including payload, and you should be able to access the corresponding attachments.
Code sample:
List<Message> list = listMessageResponse.getMessages();
for(Message m : list) {
Message message = service.users().messages().get(user, m.getId()).execute();
List<MessagePart> part = message.getPayload().getParts();
// Rest of code
}
Reference:
Class ListMessagesResponse
Users.messages: list > Response

Difficulties sending multipart/form-data request via Postman

please, this is y concern: I'll like to know how to query a web service defined as in the below code using postman for test purposes.
PS: I can't change the method signature
I have a web service like this :
#POST
#Path("/uploadOpenAccountRequestFiles/{requestId}")
#Consumes({MediaType.APPLICATION_JSON,MediaType.MULTIPART_FORM_DATA})
public Response uploadOpenAccountRequestFiles(#PathParam("requestId") String requestId,
MultipartFormDataInput multipartFormDataInput)
throws JsonGenerationException, JsonMappingException, IOException {
initContext();
logger.info("DIGIBANK : uploadOpenAccountRequestFiles END: ");
String msge = "";
try {
digiBean.saveToserver(requestId, multipartFormDataInput);
} catch (Exception e) {
msge = e.getMessage();
e.printStackTrace();
}
org.af.webservice.Response resp = new org.af.webservice.Response(
request.getSession().getId(), "uploadOpenAccountRequestFiles", "",
msge.equalsIgnoreCase("OK") ? true : false, msge.equalsIgnoreCase("OK") ? false : true, "",
msge.equalsIgnoreCase("OK") ? true : false, "Boolean", msge);
logger.info("DIGIBANK : uploadOpenAccountRequestFiles END: ");
return Response.ok().entity(mapper.writeValueAsString(resp)).build();
}
this is are images of my configurations:
enter image description here
enter image description here
For calling that service, you need to pass requestId like below:
http://localhost:8080/uploadOpenAccountRequestFiles/requestId-value-here
For sending MultiPart data such as a file, you need to select form-data option in the body section in Postman and select the file by selecting the File dropdown. Also, make sure to set the appropriate headers for the request.
Check the below stackoverflow answer for more details:
Tool for sending multipart/form-data request
The steps of uploading a file through postman along with passing some input data along with the multipart request is very well discussed in below blog along with the screenshot. In this blog, the api code is written in node js. You can go through it once. It may give some information.
https://jksnu.blogspot.com/2021/09/how-to-create-post-request-with.html

Java - YouTube API - setPublishAt on video leads to "400 Bad Request - invalidVideoMetadata"

I'm trying to save a video via the YouTube API. Basically it is already working fine, just for one exception: If I try to set the status.setPublishAt() I get
400 Bad Request
{
"code" : 400,
"errors" : [ {
"domain" : "youtube.video",
"location" : "body",
"locationType" : "other",
"message" : "The request metadata is invalid.",
"reason" : "invalidVideoMetadata"
} ],
"message" : "The request metadata is invalid."
}
The code is as follows:
YouTube.Videos.List listVideosRequest = M_YOUTUBE.videos().list("snippet,status").setId(_dbVideo.getYoutubeId());
VideoListResponse listResponse = listVideosRequest.execute();
List<Video> videoList = listResponse.getItems();
if (videoList.isEmpty()) {
return false;
}
Video video = videoList.get(0);
VideoStatus status = video.getStatus();
status.setPrivacyStatus(_dbVideo.getPrivacyStatus()); // "private"
String sPublishedAt = _dbVideo.getPublishedAt();
// sPublishAt is in ISO 8106: "2016-10-28T10:01:00.000+02:00"
if (sPublishedAt != null && sPublishedAt != "") {
// this line leads to the bad request.
status.setPublishAt(new DateTime(sPublishedAt));
} else {
status.setPublishAt(null);
}
VideoSnippet snippet = video.getSnippet();
String sTitle = _dbVideo.getTitle();
String sDescription = _dbVideo.getDescription();
String sTags = _dbVideo.getTags();
...
snippet.setTitle(sTitle);
snippet.setDescription(sDescription);
snippet.setTags(tagList);
snippet.setCategoryId(_dbVideo.getCategoryId());
YouTube.Videos.Update updateVideosRequest = M_YOUTUBE.videos().update("snippet,status", video);
updateVideosRequest.execute();
For the uploading process I am using mostly the same code and it is working there. If I decide not to set the publishAt property with a date and instead set it to null it is working fine as well. Am I missing something?
Update:
The beheviour only occurs if the video was set to public once. If i decide to set it back to private and set a new publishAt i get the BadRequest.
You have to sent all the status values, otherwise the update method will think you are trying to empty them. You have send setPrivacyStatus and publishat. But you should send all other parameter to your request. Also, if you send publishat time too close to current time it will show badRequest (400) invalidPublishAt. try to put 60 mins. It works for me.

Accessing Shoutcast Current Stream Information

i am creating an internet radio application for android. So far i have successfully streamed and played using the shoutcast url for various stations.This is my code :
String url = "http://185.33.22.13:7704";
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
try
{
mediaPlayer.setDataSource(url);
mediaPlayer.prepare();
}
catch (IOException e)
{
e.printStackTrace();
}
mediaPlayer.start();
Next i would want to get the information of stream to be shown in my application.
I want to retrieve the information shown in green box:
People have posted about FFmpegMediaMetadataRetriever but github is way out of my league to understand also tried thier apk file which does nothing when given the above http link.Please suggest me a simple and robust solution to retrieve the data from SHOUTcast DNAS status.
Thanks in advance !
Shoutcast V1 has a special page with bitrate and other information.
If your shoutcast is running on
http://185.33.22.13:7704
then this page url will be:
http://185.33.22.13:7704/7.html
The body of that page looks like this:
<HTML><meta http-equiv="Pragma" content="no-cache"></head><body>214,1,312,1000,213,64,Ferry Tayle - The Way Back Home (Edit) (feat. Poppy)</body></html>
Split that text by commas and you will get:
Current listeners
Stream status
Peak listeners
Maximum number of listeners (from server start)
Unique listeners
Bitrate
Song title
Here is an example in javascript (Nodejs) that gets the data from 7.html page and parses it:
var request = require('request'),
options = {
url: 'http://185.33.22.21:7704/7.html',
headers: {
'User-Agent': 'Mozilla/5.0'
}
},
regex = /<body>(.*?)<\/body>/i;
request(options, function (error, response, body) {
var match = regex.exec(body),
data = match[1].split(',');
console.log("7.html: ", body);
console.log("Current listeners: ", data[0]);
console.log("Stream status: ", data[1]);
console.log("Peak listeners: ", data[2]);
console.log("Maximum number of listeners: ", data[3]);
console.log("Unique listeners: ", data[4]);
console.log("Bitrate: ", data[5]);
console.log("Metada: ", data[6]);
});
Please note that setting "User-Agent" header is required.
If you have admin password from that server - then another way is to get XML data directly from shoutcast admin page using the following URL:
http://185.33.22.13:7704/admin.cgi?mode=viewxml

Java Google Calendar api "access_denied" on Service Account

I'm trying to connect to a calendar using the Java Google Calendar api. The java application uses a service account.
I've the following code:
java.io.File licenseFile = new java.io.File("39790cb51b361f51cab6940d165c6cda4dc60177-privatekey.p12");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("xxx#developer.gserviceaccount.com")
.setServiceAccountUser(EMAIL_ADRESS)
.setServiceAccountScopes(CalendarScopes.CALENDAR)
.setServiceAccountPrivateKeyFromP12File(licenseFile)
.build();
client = new com.google.api.services.calendar.Calendar.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("Google Calendar Sync").build();
Calendar calendar = client.calendars().get(EMAIL_ADRESS).execute();
On the last line I get an IOException with the message:
ex = (com.google.api.client.auth.oauth2.TokenResponseException)
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad
Request { "error" : "access_denied" }
I dubble checked the values for the GoogleCredential object and they are correct.
I've also added https://www.google.com/calendar/feeds/, http://www.google.com/calendar/feeds/ in my domain console with the application id as client to authorize third party application access
Am I forgetting a step?
The api isn't finished yet. More specifically the service account part.
The calendar owner needs to give permission to the application to read/write the calendar in it's calendar settings. It's found in the sharing settings of the calendar, there you can add e-mail adresses of accounts and give them permission on your calendar.
So in this case I had to add: xxx#developer.gserviceaccount.com to the permission/share list of the calendars the application needed access to.
I also deleted another post that didn't full work because of the issue above. I'll undelete it since it contains some code fragments that may help other people in the future. But beaware of the permission issues and service accounts not supporting Google Calendar
I do:
List find = client.events().list(EMAIL_ADRESS);
DateTime timeMin = new DateTime(DateUtil.stringToDate("01/01/2013"));
DateTime timeMax = new DateTime(DateUtil.stringToDate("01/02/2013"));
find.setTimeMin(timeMin);
find.setTimeMax(timeMax);
try{
Events events = find.execute();
int i =0;
while (true) {
System.out.println("Page: "+(++i)+": "+events.getItems().size());
for (Event event : events.getItems()) {
System.out.println(event.getSummary());
}
String pageToken = events.getNextPageToken();
if (pageToken != null && !pageToken.isEmpty()) {
events = client.events().list(EMAIL_ADRESS).setPageToken(pageToken).execute();
} else {
break;
}
}
}catch(GoogleJsonResponseException e){
System.out.println(e.getMessage());
// e.printStackTrace();
}
I got it to work
I don't know why but I deleted the line
.setServiceAccountUser(EMAIL_ADRESS)
Also I added an extra url in the domain scope:
https://apps-apis.google.com/a/feeds/calendar/resource/#readonly
and deleted a link that wasn't working.
Finally I changed the applicationName in my client declaration to the same name as in the api console
client = new com.google.api.services.calendar.Calendar.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("HolidaySyncs").build();
After these steps it started to work.
Also note for future reference after I did this I had the following error:
{
"error": {
"errors": [
{
"domain": "global",
"reason": "notFound",
"message": "Not Found"
}
],
"code": 404,
"message": "Not Found"
}
}
I solved this by changing for example:
Event result = client.events().insert(<EMAIL ADRESS>, event).execute();
to
Event result = client.events().insert("primary", event).execute();
First I tought there was something wrong with the google servers, but apparently it goes wrong when you try to link to a calendar ID. So linking to "primary" which is the primary calendar of an account works. But according to the documentation it should also work when you refer to a specific calendar ID, where the email address is the primary calendar. Probably a bug?
UPDATE: after these code correction I still had issues. Read the accepted answer for more information.

Categories