I'm trying to Use Youtube api v3 to comment on video and getting this error but my request data is correct according to documentation.
Here is my code.
Using oauth the code setting access_token like this
oauth.setCredentials(tokens);
var channelId = "UCq-Fj5jknLsUf-MWSy4_brA";
var request = Youtube.commentThreads.insert({
"part": [
"snippet"
],
"resource": {
"snippet": {
"videoId": "qfuFeUnAm8E",
"topLevelComment": {
"snippet": {
"textOriginal": "best video"
}
},
"channelId": channelId
}
}
}, (err, data) => {
if (err) {
console.log(err, 'errerrerr')
}
if (data) {
console.log(data, 'datadata');
}
});
This is the error i'm getting in return
errors: [
{
message: "The API server failed to successfully process the request. While this can be a transient error, it usually indicates that the request's input is invalid. Check the structure of the <code>commentThread</code> resource in the request body to ensure that it is valid.",
domain: 'youtube.commentThread',
reason: 'processingFailure',
location: 'body',
locationType: 'other'
}
]
This is the authentication or authorization code generating everytime
"tokens": {
"access_token": "[redacted]",
"scope": "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtubepartner https://www.googleapis.com/auth/youtube.force-ssl https://www.googleapis.com/auth/youtube.upload",
"token_type": "Bearer",
"expiry_date": 1655195240477
}
I think you should consider testing out your insert in the try me The object you have created doesnt look right at all.
You should consult comments resource for the proper format of the body.
<script src="https://apis.google.com/js/api.js"></script>
<script>
/**
* Sample JavaScript code for youtube.comments.insert
* See instructions for running APIs Explorer code samples locally:
* https://developers.google.com/explorer-help/code-samples#javascript
*/
function authenticate() {
return gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/youtube.force-ssl"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
function loadClient() {
gapi.client.setApiKey("YOUR_API_KEY");
return gapi.client.load("https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest")
.then(function() { console.log("GAPI client loaded for API"); },
function(err) { console.error("Error loading GAPI client for API", err); });
}
// Make sure the client is loaded and sign-in is complete before calling this method.
function execute() {
return gapi.client.youtube.comments.insert({
"resource": {
"snippet": {
"videoId": "qfuFeUnAm8E",
"channelId": "UCq-Fj5jknLsUf-MWSy4_brA"
}
}
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
gapi.load("client:auth2", function() {
gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
});
</script>
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>
Related
Trying to get this to work and GET / Patch work just fine, but POST gives me HTTP STATUS 400 and 403. Must be something with scopes. In Azure AD I have set the following scopes:
Mail.ReadWrite (Delegated)
Mail.ReadWrite (Application)
Mail.Send Delegated)
Mail.Send (Application)
So, signing in works just fine, getting / patching messages as well. Only POST doesnt seem to work.
See code for exact error messages.
Angular10
App.module
export function MSALInstanceFactory(): IPublicClientApplication {
return new PublicClientApplication({
auth: {
clientId: 'xxxx',
authority: 'https://login.microsoftonline.com/common/',
redirectUri: '/',
postLogoutRedirectUri: '/#/login'
},
cache: {
cacheLocation: BrowserCacheLocation.LocalStorage,
storeAuthStateInCookie: isIE, // set to true for IE 11
},
system: {
loggerOptions: {
loggerCallback,
logLevel: LogLevel.Info,
piiLoggingEnabled: false
}
}
});
}
export function MSALInterceptorConfigFactory(): MsalInterceptorConfiguration {
const protectedResourceMap = new Map<string, Array<string>>();
protectedResourceMap.set('https://graph.microsoft.com/v1.0/me', ['user.read', 'mail.readWrite', 'email']);
// also tried these scopes ..
// protectedResourceMap.set('https://graph.microsoft.com/v1.0', ['user.read', 'mail.readWrite', 'email']);
// protectedResourceMap.set('https://graph.microsoft.com/v1.0/query', ['user.read', 'mail.readWrite', 'email']);
// protectedResourceMap.set('https://graph.microsoft.com/v1.0/search/query', ['user.read', 'mail.readWrite', 'email']);
return {
interactionType: InteractionType.Redirect,
protectedResourceMap
};
}
export function MSALGuardConfigFactory(): MsalGuardConfiguration {
return { interactionType: InteractionType.Redirect };
}
#NgModule({
imports: [
BrowserModule,
// etc..
],
declarations: [AppComponent],
providers: [
NgEventBus,
ChhServices,
SynclogService,
AppService,
AuthService,
GapiServices,
{
provide: ErrorHandler,
useClass: ErrorService,
},
{
provide: HTTP_INTERCEPTORS,
useClass: MsalInterceptor,
multi: true
},
{
provide: MSAL_INSTANCE,
useFactory: MSALInstanceFactory
},
{
provide: MSAL_GUARD_CONFIG,
useFactory: MSALGuardConfigFactory
},
{
provide: MSAL_INTERCEPTOR_CONFIG,
useFactory: MSALInterceptorConfigFactory
},
MsalService,
MsalGuard,
MsalBroadcastService
],
bootstrap: [AppComponent],
})
export class AppModule { }
Auth.service
signIn() {
console.log('AuthService::signIn');
this.msalService.loginPopup().subscribe((result) => {
this.accessToken = result['accessToken'];
console.log('authority', result, this.accessToken);
});
}
testGraphApi() {
// 200 OK
const apiGet = this.httpClient.get(`https://graph.microsoft.com/v1.0/me/messages/`).subscribe((data) => {
console.log('get', '/me/messages', data);
});
const categories: any[] = ['custom'];
const body = {
subject: '2320, with tags',
flag: { flagStatus: 'flagged' }, // notFlagged
categories,
body: {
contentType: 'html',
content: 'lalala'
},
inferenceClassification: 'other'
};
const id = 'AQMkADAwATM3ZmYAZS0zOTkANy02MTAwAC0wMAItMDAKAEYAAAM_TfJTK-tISYhjZdaCkkbgBwCPpkVcscQ9QJF-EDzB8h_oAAACAQwAAACPpkVcscQ9QJF-EDzB8h_oAAACHbIAAAA=';
// 200 OK
const apiPatch = this.httpClient.patch(`https://graph.microsoft.com/v1.0/me/messages/${id}`, body).subscribe((data) => {
console.log('patch', '/me/messages', data);
});
const bodySendMail = {
'message': {
'subject': 'Meet for lunch?',
'body': {
'contentType': 'Text',
'content': 'The new cafeteria is open.'
},
// etc..
}
}
const headers = new HttpHeaders({ 'Content-Type': 'application/json', 'Authorization': `Bearer ${this.accessToken}` });
// 403 Forbidden
// "code": "ErrorAccessDenied",
// "message": "Access is denied. Check credentials and try again.",
const apiSendMail = this.httpClient.post(`https://graph.microsoft.com/v1.0/me/sendMail`, bodySendMail, { headers }).subscribe((data) => {
console.log('post', '/me/sendMail', data);
});
const bodySearch = {
'requests': [
{
'entityTypes': [
'message'
],
'query': {
'queryString': 'ref:6019d6bf1ce3425fb833559e'
},
'from': 0,
'size': 5
}
]
}
// 400 Bad Request
// "code": "AuthenticationError",
// "message": "Error authenticating with resource",
const apiSearch = this.httpClient.post(`https://graph.microsoft.com/v1.0/search/query`, bodySearch, { headers }).subscribe((data) => {
console.log('post', '/search/query', data);
});
}
// 403 Forbidden
// "code": "ErrorAccessDenied",
// "message": "Access is denied. Check credentials and try again."
Send mail API needs Mail.Send permission. When requesting /me endpoint which bases the current signed-in user, it should have the delegated permission.
So you need to add Mail.Send of delegated permission in the portal and add it in your code.
// 400 Bad Request
// "code": "AuthenticationError",
// "message": "Error authenticating with resource"
searchEntity: query API needs the Mail.ReadWrite delegated permission. This api only supports "work or school account". A work account typically uses an organization’s custom domain name or company name, such as "jon#contoso.com" or "xxx#yourTenantName.onmicrosoft.com".
You could test to request the api in Graph Explorer.
I need to get list of best songs based on genre.
For example if i send request with keyword "pop" the it should return me list of music from that genre sorted by popularity (views).
And next question would be can i use that list as playlist for radio, I mean would it be playable audio or i will just get list of songs as string?
I try to generate code, but I don´t know all details for request:
<script src="https://apis.google.com/js/api.js"></script>
<script>
/**
* Sample JavaScript code for youtube.channels.list
* See instructions for running APIs Explorer code samples locally:
* https://developers.google.com/explorer-help/guides/code_samples#javascript
*/
function authenticate() {
return gapi.auth2.getAuthInstance()
.signIn({scope: "https://www.googleapis.com/auth/youtube.readonly"})
.then(function() { console.log("Sign-in successful"); },
function(err) { console.error("Error signing in", err); });
}
function loadClient() {
gapi.client.setApiKey("YOUR_API_KEY");
return gapi.client.load("https://www.googleapis.com/discovery/v1/apis/youtube/v3/rest")
.then(function() { console.log("GAPI client loaded for API"); },
function(err) { console.error("Error loading GAPI client for API", err); });
}
// Make sure the client is loaded and sign-in is complete before calling this method.
function execute() {
return gapi.client.youtube.channels.list({
"part": "snippet",
"categoryId": "music",
"maxResults": 25
})
.then(function(response) {
// Handle the results here (response.result has the parsed body).
console.log("Response", response);
},
function(err) { console.error("Execute error", err); });
}
gapi.load("client:auth2", function() {
gapi.auth2.init({client_id: "YOUR_CLIENT_ID"});
});
</script>
<button onclick="authenticate().then(loadClient)">authorize and load</button>
<button onclick="execute()">execute</button>
and because of that i get error:
{
"error": {
"errors": [
{
"domain": "youtube.channel",
"reason": "categoryNotFound",
"message": "Channel category not found.",
"locationType": "parameter",
"location": "categoryId"
}
],
"code": 404,
"message": "Channel category not found."
}
}
"categoryId": "music" - doesn't look valid for me.
Please search what's the category for "music" - in the request videoCategories, in region US, the videoCategoryId is 10.
My suggestion is:
Change this value:
"categoryId": "music",
To:
"categoryId": "10", -- Changed value
I have been trying to connect an assistant action to my backend server
I am using my own Oauth server and followed the instructions on
https://developers.google.com/actions/identity/oauth2?oauth=code
I am using actions_intent_Sign_in for my dialogflow event intent (like https://actions-on-google.github.io/actions-on-google-nodejs/classes/conversation_helper.signin.html)
when i use my action to sign in, i get the login window to my server, i do the account linking and i can see that i generated the tokens on my server but i cant find the token in (conv.user.access.token)
and this is the code for my intent using "actions on google sdk "
'use strict';
var _ = require('lodash');
var path = require('path')
var express = require('express')
var http = require('http')
const bodyParser = require('body-parser');
var expressApp = express().use(bodyParser.json());
var server = http.createServer(expressApp).listen(3000)
const {
dialogflow,
SignIn
} = require('actions-on-google');
const app = dialogflow({
debug: true,
clientId: '7b4a6dfc-4b35-11e9-8646-d663bd873d93'
});
app.intent('Start Sign-in', conv => {
conv.ask(new SignIn());
});
app.intent('Get Sign-in', (conv, params, signin) => {
console.log("get sign in ");
console.log(JSON.stringify(signin));
if (signin.status === 'OK') {
const access = conv.user.access.token
console.log("the access token is " + access);
conv.ask('Great, thanks for signing in! What do you want to do next?');
} else {
conv.ask('I wont be able to save your data, but what do you want to do next?.');
}
});
and the response comes back as
{"#type":"type.googleapis.com/google.actions.v2.SignInValue","status":"OK"}
the access token is undefined
Response {
"status": 200,
"headers": {
"content-type": "application/json;charset=utf-8"
},
"body": {
"payload": {
"google": {
"expectUserResponse": true,
"richResponse": {
"items": [
{
"simpleResponse": {
"textToSpeech": "Great, thanks for signing in! What do you want to do next?"
}
}
]
}
}
}
}
}
the user object of conv has only this data
"user": {
"raw": {
"lastSeen": "2019-03-20T12:46:23Z",
"locale": "en-US",
"userId": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst"
},
"storage": {},
"_id": "okdhyeGSk5tofgLjEepIUrA6mmewCESY8MjklZRPvQJgv6-uybfPobwdfgtrGZJ3bE2sM9ninhst",
"locale": "en-US",
"permissions": [],
"last": {
"seen": "2019-03-20T12:46:23.000Z"
},
"name": {},
"entitlements": [],
"access": {},
"profile": {}
}
i dont know where the access/refresh token can be found or if there is any requirement for the post to send from my oauth server that i missed
so finally i managed to get it working with the help of Actions on Google Support Team
the problem was me having another google account logged-in in another tab, even though i had the AoG and dialogflow agent connected with the same account
tried all using incognito window and it works
When performing a search request to the API, the height & width fields of the thumbnails are not included in the response, even when specified in the fields parameter. Here's an example for a JS object that provides the API parameters:
{
part: 'snippet',
type: 'video',
order: 'relevance',
q: 'test',
fields: 'items(snippet(thumbnails(high(url,height,width))))'
}
Which translates into the following request URL:
https://www.googleapis.com/youtube/v3/search?order=relevance&part=snippet&q=test&fields=items(snippet(thumbnails(high(url%2Cheight%2Cwidth))))&type=video&key={YOUR_API_KEY}
This call yields the following response without the width or height of the thumbnails.
{
"items": [
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/3HKs8WTGzw8/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/vW_8K_mLtsU/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/4Yk-jd4BHys/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/HU9mnag7vSM/hqdefault.jpg"
}
}
}
},
{
"snippet": {
"thumbnails": {
"high": {
"url": "https://i.ytimg.com/vi/pyrH7b0zHwU/hqdefault.jpg"
}
}
}
}
]
}
This similarly does not work for the default or medium thumbnail keys either.
How can these fields be retrieved?
The search endpoint won't return those details. You'll have to take the IDs returned from the search and do another API call to the videos endpoint for the snippet. For instance
https://www.googleapis.com/youtube/v3/videos?part=snippet&id={VIDEO_ID}&key={YOUR_API_KEY}
As per Youtube Date API (v3) Search Method returns as per document but its not working properly.
Now you should try alternative method by the API call of Video.
URL :- https://www.googleapis.com/youtube/v3/videos?part=snippet&id={VIDEO_ID}&key={YOUR_API_KEY}
VIDEO_ID = Return by Search API
YOUR_API_KEY = Google Project API key
Try it
$.get(
"https://www.googleapis.com/youtube/v3/search",{
order:'relevance',
part : 'snippet',
type : 'video',
q: 'test',
key: 'XXXXXXX'},
function(data) {
alert(data.items.length);
$.each( data.items, function( i, item ) {
pid = item.id.videoId;
getVids(pid);
});
}
);
//Get Videos
function getVids(pid){
$.get(
"https://www.googleapis.com/youtube/v3/videos",{
part : 'snippet',
id : pid,
key: 'XXXXXXXX'},
function(data) {
//Code whatever you want
}
);
}
Using loopback, I have created a connection to an existing API using the REST connector, which is working well. I would however like to pass through the oAuth token coming from the client.
I can get hold of the oAuth token by grabbing ctx.req.headers.authorization from the Model.beforeRemote method, but can't seem to figure out a way of passing it to the REST connector as a new header.
I've tried a couple of things:
Adding a hook using Model.observe (but this doesn't seem to fire with the REST connector).
Using a template with an authorization field - but have not been able to get this working correctly.
Any ideas appreciated.
With the connector below you should be able to pass the OAuth token into the function (as first parameter in the example). Does something like this not work for you?
{
connector: 'rest',
debug: false,
options: {
"headers": {
"accept": "application/json",
"content-type": "application/json",
"authorization": "{oauth}"
},
strictSSL: false,
},
operations: [
{
template: {
"method": "GET",
"url": "http://maps.googleapis.com/maps/api/geocode/{format=json}",
"query": {
"address": "{street},{city},{zipcode}",
"sensor": "{sensor=false}"
},
"options": {
"strictSSL": true,
"useQuerystring": true
},
"responsePath": "$.results[0].geometry.location"
},
functions: {
"geocode": ["oauth", "street", "city", "zipcode"]
}
}
]}
Wanted to answer this, and build on Bryan's comments. Firstly, in datasources.json, you'll want to setup the REST connector:
{
"name": "connect",
"connector": "rest",
"debug": "true",
"operations": [
{
"template": {
"method": "GET",
"url": "http://server/api",
"headers":{
"authorization": "Bearer {token}"
}
},
"functions": {
"get": ["token"]
}
}
]
}
As Bryan covered, it possible to put the auth header in each call, or at the root of the connector.
Secondly, and this is the bit I was stuck on, in order to pass the token to the API call from a model, it's required to generate a remote method that passes the token as a query parameter. This is what it looks like in this example:
module.exports = function (Model) {
Model.disableRemoteMethod('invoke', true);
Model.disableRemoteMethod('get', true);
Model.call = function (req, cb) {
var token = req.token;
Model.get(token, function (err, result) {
cb(null, result);
});
};
Model.remoteMethod(
'call',
{
http: {path: '/', verb: 'get'},
accepts: [
{arg: 'req', type: 'object', http: {source: 'req'}}
],
returns: {
root: true
}
}
);
};
Notice how the req argument is required in order to provide the request to the model. You also notice that I've disabled the original get and invoke methods (replacing it with a more REST-friendly resource).
Finally, you'll need to get the token into the request. For this, it's easy enough to use some middleware. Here's an example from server.js:
app.use('/api', function (req, res, next) {
oidc.authenticate(req, function (err, token) {
if (err) {
return res.send({status: 401, message: err});
}
req.token = token;
next();
});
});
In the above example, I'm using an internal OIDC provider to validate the token, but of course, you can use anything.