googleapis / google-api-nodejs-client

Google's officially supported Node.js client library for accessing Google APIs. Support for authorization and authentication with OAuth 2.0, API Keys and JWT (Service Tokens) is included.
https://googleapis.dev/nodejs/googleapis/latest/
Apache License 2.0
11.36k stars 1.92k forks source link

Hangout Chat API Documentation #1123

Closed Abhi347 closed 5 years ago

Abhi347 commented 6 years ago

Hangout Chat API is supported in the latest client library, but there's no documentation for that. Can we have a documentation for Hangout Chat API too, especially for the asynchronous calls?

JustinBeckwith commented 6 years ago

👋 greetings! There are a few good reference docs you can use for Chat. FIrst, here's the developer guide: https://developers.google.com/hangouts/chat/

And for a reference of how to use the API: http://google.github.io/google-api-nodejs-client/modules/_apis_chat_v1_.html

Does this get you where you need to go? Or are there are specific things you're looking for? Hope this helps!

Abhi347 commented 6 years ago

Hi Justin, thanks for replying. I have the Chat API working using the REST API, but I was looking for the documentation of the node.js client, just like how I use sheets API. I tried using the nodejs client, but it started giving me some strange errors. Let me recreate them - Here's my code, just to give you an idea

/* eslint-disable no-console */
const url = require('url');
const request = require('request-promise-native');
const CHAT_ENDPOINT = 'https://chat.googleapis.com';

async function sendMessageUsingRestApi(apiKey, threadName, message) {
  let threadParts = threadName.split('/');
  let spaces = threadParts[1];
  let threadKey = threadParts[3];
  const auth = apiKey;
  let endPointUrl = url.resolve(
    CHAT_ENDPOINT,
    `/v1/spaces/${spaces}/messages?threadKey=${threadKey}&key=${auth}`
  );
  const chatApiResponse = await request.post(endPointUrl, {
    json: { text: message }
  });
  return chatApiResponse;
}

const { google } = require('googleapis');
const chat = google.chat('v1');
const { promisify } = require('util');
chat.spaces.messages.createAsync = promisify(chat.spaces.messages.create);

async function sendMessageUsingClient(apiKey, threadName, message) {
  let threadParts = threadName.split('/');
  let spaces = threadParts[1];
  let threadKey = threadParts[3];
  const auth = apiKey;
  const chatApiResponse = await chat.spaces.messages.createAsync({
    auth: auth,
    parent: `spaces/${spaces}`,
    threadKey: threadKey,
    body: {
      text: message
    }
  });
  return chatApiResponse;
}

sendMessageUsingRestApi(
  'MY_API_KEY',
  'spaces/<spaceID>/threads/<threadKey>',
  'Hi From Rest'
)
  .then(response => console.log(response))
  .catch(e => console.error(e));

sendMessageUsingClient(
  'MY_API_KEY',
  'spaces/<spaceID>/threads/<threadKey>',
  'Hi From Client'
)
  .then(response => console.log(response))
  .catch(e => console.error(e));

So, the REST API works and I get the message Hi From Rest in my chat window, however the nodejs client gives me error, just because it's not clear which parameter will go where. I have tried a lot and finally decided to use the REST API. The problem with that is, I have to use an API key, instead of the Service Account. That's why I think there should be a proper documentation of using Chat API using the Nodejs client.

Here's the error I am getting just in case it helps -

{Error: <h1>Not Found</h1>
<h2>Error 404</h2>

    at createError (/Volumes/Projects/Work/node_modules/axios/lib/core/createError.js:16:15)
    at settle (/Volumes/Projects/Work/node_modules/axios/lib/core/settle.js:18:12)
    at Unzip.handleStreamEnd (/Volumes/Projects/Work/node_modules/axios/lib/adapters/http.js:201:11)
    at emitNone (events.js:111:20)
    at Unzip.emit (events.js:208:7)
    at endReadableNT (_stream_readable.js:1055:12)}
    at _combinedTickCallback (internal/process/next_tick.js:138:11)
    at process._tickCallback (internal/process/next_tick.js:180:9)
lcundiff commented 6 years ago

I added the service authentication code to yours and the REST API call works well with it! Just make a service account and download the JSON credentials file and put it in your directory. However, I only got this to work with REST API call, if I make any further progress later, I can update this comment.

(I commented out the token check function because the callback function was giving me an issue)

`const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const request = require('request-promise-native');

// If modifying these scopes, delete credentials.json.
const SCOPES = ['https://www.googleapis.com/auth/script.projects'];
const TOKEN_PATH = 'credentials.json';

// Load client secrets from a local file.
fs.readFile('client_secret.json', (err, content) => {
  if (err) return console.log('Error loading client secret file:', err);
  // Authorize a client with credentials, then make the REST API call.
  authorize(JSON.parse(content), sendMessageUsingRestApi('<API key>','spaces/<space ID>','This is sent using REST API'));
});

/**
 * Create an OAuth2 client with the given credentials, and then execute the
 * given callback function.
 * @param {Object} credentials The authorization client credentials.
 * @param {function} callback The callback to call with the authorized client.
 */
function authorize(credentials, callback) {
  const {client_secret, client_id, redirect_uris} = credentials.installed;
  const oAuth2Client = new google.auth.OAuth2(
      client_id, client_secret, redirect_uris[0]);

     /* 
  // Check if we have previously stored a token.
  fs.readFile(TOKEN_PATH, (err, token) => {
    if (err) {
        return getAccessToken(oAuth2Client, callback);
    }
    oAuth2Client.setCredentials(JSON.parse(token));
    if(callback){
        callback(oAuth2Client);
    }
  });
  */
}

/**
 * Get and store new token after prompting for user authorization, and then
 * execute the given callback with the authorized OAuth2 client.
 * @param {google.auth.OAuth2} oAuth2Client The OAuth2 client to get token for.
 * @param {getEventsCallback} callback The callback for the authorized client.
 */
function getAccessToken(oAuth2Client, callback) {
  const authUrl = oAuth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
  });
  console.log('Authorize this app by visiting this url:', authUrl);
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  rl.question('Enter the code from that page here: ', (code) => {
    rl.close();
    oAuth2Client.getToken(code, (err, token) => {
      if (err) return callback(err);
      oAuth2Client.setCredentials(token);
      // Store the token to disk for later program executions
      fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
        if (err) console.error(err);
        console.log('Token stored to', TOKEN_PATH);
      });
      callback(oAuth2Client);
    });
  });
}`
Abhi347 commented 6 years ago

Thanks @lcundiff for the code. I would however really appreciate if we can make this work with the nodejs client also. I hope somebody just updates the docs somewhere.

jc21 commented 6 years ago

I'm also having issues understanding the docs provided for Chat. Despite the auth issues which I've managed to work out elsewhere, I simply can't create a message and send to a space.

const chat = google.chat({version: 'v1', auth: jwtClient});

chat.spaces.messages.create({
    parent: space.name,
    body: {
        text: 'hello world'
    }
})
.then(res => {
    console.log('Response:', res.data);
})
.catch(err => {
    console.error('Error:', err);
});

Which throws: Invalid JSON payload received. Unknown name "body[text]": Cannot bind query parameter. Field \'body[text]\' could not be found in request message.

The debug of the error says that the POST request is: POST /v1/spaces/6WNdlAAAAAE/messages?body%5Btext%5D=hello%20world without any POST body.

An example in a comment above used this body.text item and it was reported to work but I can't see how it ever worked. Also, when I put that body.text as the second argument to the create function, it complains that Message cannot be empty.

There are numerous samples for other Google services, why not Chat?

jc21 commented 6 years ago

To update anyone else looking for a solution to this, I dug deep in to the google source and discovered that the following create method signature works:

chat.spaces.messages.create({
    parent: 'spaces/xxxxxx',
    requestBody: {
        text: 'hello world'
    }
})
lcundiff commented 6 years ago

@jc21 Awesome. Have you done anything using the threadKey query parameter when creating a message? Trying to continuously update a message instead of sending new ones using messages.update (but it requires threadKey.

lcundiff commented 6 years ago

Found a work around. Just am using the message ID pulled from the message JSON. The thread ID is in the message JSON too, but realized thread ID is used for "replying" to a message while message ID (name attribute in the JSON) is used for updating! So only use threadID for using the chat reply feature.

grant commented 5 years ago

I think this issue can be closed. We have some docs and samples like these ones: https://developers.google.com/hangouts/chat/reference/rest/ https://github.com/gsuitedevs/hangouts-chat-samples/tree/master/node

grant commented 5 years ago

@JustinBeckwith Close this issue?

Abdulwasa commented 4 years ago

What comes after spaces in the Parent key Object @jc21 ? Can you help me please ?

lanmower commented 3 years ago

Hangouts endpoints is real amateur hour for google.

3 years and there is still no working docs, you have to poke your way through the dark using 3 different sdk's to get a bot to do one thing.

I'm now 12 hours deep implementing a google chat bot, and I've just gotten it to send its first message, how long does this take with telegram/discord? under 60 seconds.

Closing this issue was a huge mistake, do we have to just thumbsuck that requestBody is needed for the node.js sdk to work? this is a sad sad day for google and their teams, I feel embarrassed that I recommended these services 6 years ago to these clients after this past year of VAS software implmentations, formats keep changing, services constantly get breaking changes, undocumented changes to security protocol, the docs are swiss cheese.

If anybody is scrolling through this thread concerning this issue and thinks the closed status means the discussion cant help you, please re-read this discussion, if you're getting message can not be empty errors and then errors when you try and fill in the text param, you need requestBody in your request object 🤦‍♀️🤦‍♂️