Node.js की मदद से, Google Chat के लिए इंटरैक्टिव पोल ऐप्लिकेशन बनाना

1. परिचय

Google Chat ऐप्लिकेशन, आपकी सेवाओं और संसाधनों को सीधे तौर पर Google Chat में उपलब्ध कराते हैं. इससे उपयोगकर्ता, बातचीत छोड़े बिना जानकारी पा सकते हैं और तुरंत कार्रवाई कर सकते हैं.

इस कोडलैब में, Node.js और Cloud Functions का इस्तेमाल करके, पोल ऐप्लिकेशन बनाने और उसे डिप्लॉय करने का तरीका बताया गया है.

इस इमेज में, एक ऐप्लिकेशन को पोल पोस्ट करते हुए दिखाया गया है. इसमें स्पेस के सदस्यों से पूछा गया है कि क्या ऐप्लिकेशन शानदार हैं. साथ ही, इंटरैक्टिव कार्ड मैसेज की मदद से वोट इकट्ठा किए जा रहे हैं.

आपको क्या सीखने को मिलेगा

  • Cloud Shell का इस्तेमाल करना
  • Cloud Functions पर डिप्लॉय करना
  • स्लैश कमांड और डायलॉग की मदद से उपयोगकर्ता से इनपुट पाना
  • इंटरैक्टिव कार्ड बनाना

2. सेटअप और ज़रूरी शर्तें

Google Cloud प्रोजेक्ट बनाएं. इसके बाद, उन एपीआई और सेवाओं को चालू करें जिनका इस्तेमाल Chat ऐप्लिकेशन करेगा

ज़रूरी शर्तें

Google Chat ऐप्लिकेशन डेवलप करने के लिए, आपके पास Google Workspace खाता होना चाहिए. साथ ही, आपके पास Google Chat को ऐक्सेस करने की अनुमति होनी चाहिए. अगर आपके पास पहले से Google Workspace खाता नहीं है, तो एक खाता बनाएं और इस कोडलैब को शुरू करने से पहले साइन इन करें.

अपनी स्पीड से एनवायरमेंट सेट अप करना

  1. Google Cloud Console खोलें और एक प्रोजेक्ट बनाएं.

    'कोई प्रोजेक्ट चुनें' मेन्यू'नया प्रोजेक्ट' बटनप्रोजेक्ट आईडी

    प्रोजेक्ट आईडी याद रखें. यह Google Cloud के सभी प्रोजेक्ट के लिए एक यूनीक नाम होता है. ऊपर दिया गया नाम पहले ही इस्तेमाल किया जा चुका है. इसलिए, यह आपके लिए काम नहीं करेगा. माफ़ करें! इस कोड लैब में इसे बाद में PROJECT_ID के तौर पर दिखाया जाएगा.
  1. इसके बाद, Google Cloud संसाधनों का इस्तेमाल करने के लिए, Cloud Console में बिलिंग चालू करें.

इस कोडलैब को पूरा करने में ज़्यादा खर्च नहीं आएगा. कोड लैब के आखिर में दिए गए "क्लीन अप" सेक्शन में दिए गए निर्देशों का पालन करना न भूलें. इसमें आपको संसाधनों को बंद करने का तरीका बताया गया है, ताकि इस ट्यूटोरियल के बाद आपसे बिलिंग न की जाए. Google Cloud के नए उपयोगकर्ता, 300 डॉलर के क्रेडिट के साथ मुफ़्त में आज़माने की सुविधा पा सकते हैं.

Google Cloud Shell

Google Cloud को अपने लैपटॉप से रिमोटली ऐक्सेस किया जा सकता है. हालांकि, इस कोडलैब में हम Google Cloud Shell का इस्तेमाल करेंगे. यह Google Cloud में चलने वाला कमांड लाइन एनवायरमेंट है.

Cloud Shell चालू करें

  1. Cloud Console में, Cloud Shell चालू करें Cloud Shell आइकॉन पर क्लिक करें.

    मेन्यू बार में Cloud Shell का आइकॉन

    Cloud Shell को पहली बार खोलने पर, आपको एक वेलकम मैसेज दिखता है. इसमें Cloud Shell के बारे में जानकारी होती है. अगर आपको वेलकम मैसेज दिखता है, तो जारी रखें पर क्लिक करें. वेलकम मैसेज दोबारा नहीं दिखता. यह रहा वेलकम मैसेज:

    Cloud Shell का वेलकम मैसेज

    Cloud Shell को चालू करने और उससे कनेक्ट होने में सिर्फ़ कुछ सेकंड लगेंगे. कनेक्ट करने के बाद, आपको Cloud Shell टर्मिनल दिखेगा:

    Cloud Shell टर्मिनल

    इस वर्चुअल मशीन में, आपके काम के सभी डेवलपमेंट टूल मौजूद हैं. यह 5 जीबी की होम डायरेक्ट्री उपलब्ध कराता है और Google Cloud में काम करता है. इससे नेटवर्क की परफ़ॉर्मेंस और पुष्टि करने की प्रोसेस बेहतर होती है. इस कोडलैब में मौजूद सभी टास्क, ब्राउज़र या Chromebook की मदद से पूरे किए जा सकते हैं.Cloud Shell से कनेक्ट होने के बाद, आपको दिखेगा कि आपने पहले ही पुष्टि कर ली है और प्रोजेक्ट को आपके प्रोजेक्ट आईडी पर पहले ही सेट कर दिया गया है.
  2. पुष्टि करने के लिए कि आपने पुष्टि कर ली है, Cloud Shell में यह कमांड चलाएं:
    gcloud auth list
    
    अगर आपको Cloud Shell को GCP API कॉल करने की अनुमति देने के लिए कहा जाता है, तो अनुमति दें पर क्लिक करें.

    कमांड का आउटपुट
    Credentialed Accounts
    ACTIVE  ACCOUNT
    *       <my_account>@<my_domain.com>
    
    अगर आपका खाता डिफ़ॉल्ट रूप से नहीं चुना गया है, तो यह कमांड चलाएं:
    $ gcloud config set account <ACCOUNT>
    
  1. पुष्टि करें कि आपने सही प्रोजेक्ट चुना हो. Cloud Shell में यह कमांड चलाएं:
    gcloud config list project
    
    कमांड आउटपुट
    [core]
    project = <PROJECT_ID>
    
    अगर सही प्रोजेक्ट नहीं मिलता है, तो इस निर्देश का इस्तेमाल करके उसे सेट किया जा सकता है:
    gcloud config set project <PROJECT_ID>
    
    कमांड आउटपुट
    Updated property [core/project].
    

इस कोडलैब को पूरा करने के दौरान, आपको कमांड लाइन ऑपरेशन का इस्तेमाल करना होगा और फ़ाइलों में बदलाव करना होगा. फ़ाइल में बदलाव करने के लिए, Cloud Shell के बिल्ट-इन कोड एडिटर, Cloud Shell Editor का इस्तेमाल किया जा सकता है. इसके लिए, Cloud Shell टूलबार की दाईं ओर मौजूद, एडिटर खोलें पर क्लिक करें. Cloud Shell में Vim और Emacs जैसे लोकप्रिय एडिटर भी उपलब्ध हैं.

3. Cloud Functions, Cloud Build, और Google Chat API चालू करें

Cloud Shell से, इन एपीआई और सेवाओं को चालू करें:

gcloud services enable \
  cloudfunctions \
  cloudbuild.googleapis.com \
  chat.googleapis.com

इस प्रोसेस को पूरा होने में कुछ समय लग सकता है.

टास्क पूरा होने पर, आपको इस तरह का मैसेज दिखेगा:

Operation "operations/acf.cc11852d-40af-47ad-9d59-477a12847c9e" finished successfully.

4. Chat ऐप्लिकेशन बनाना

प्रोजेक्ट को शुरू करना

शुरू करने के लिए, आपको एक सामान्य "Hello world" ऐप्लिकेशन बनाना होगा और उसे डिप्लॉय करना होगा. चैट ऐप्लिकेशन, वेब सेवाएं होती हैं. ये https अनुरोधों का जवाब देती हैं और JSON पेलोड के साथ जवाब देती हैं. इस ऐप्लिकेशन के लिए, Node.js और Cloud Functions का इस्तेमाल किया जाएगा.

Cloud Shell में, poll-app नाम की नई डायरेक्ट्री बनाएं और उस पर जाएं:

mkdir ~/poll-app
cd ~/poll-app

कोडलैब का बाकी काम और बनाई जाने वाली फ़ाइलें, इसी डायरेक्ट्री में होंगी.

Node.js प्रोजेक्ट शुरू करें:

npm init

NPM, प्रोजेक्ट कॉन्फ़िगरेशन के बारे में कई सवाल पूछता है. जैसे, नाम और वर्शन. हर सवाल के लिए, डिफ़ॉल्ट वैल्यू स्वीकार करने के लिए ENTER दबाएं. डिफ़ॉल्ट एंट्री पॉइंट, index.js नाम की फ़ाइल होती है. इसे हम अगले चरण में बनाएंगे.

Chat ऐप्लिकेशन का बैकएंड बनाना

अब ऐप्लिकेशन बनाना शुरू करें. यहां दिए गए कॉन्टेंट के साथ index.js नाम की फ़ाइल बनाएं:

/**
 * App entry point.
 */
exports.app = async (req, res) => {
  if (!(req.method === 'POST' && req.body)) {
      res.status(400).send('')
  }
  const event = req.body;
  let reply = {};
  if (event.type === 'MESSAGE') {
    reply = {
        text: `Hello ${event.user.displayName}`
    };
  }
  res.json(reply)
}

फ़िलहाल, यह ऐप्लिकेशन ज़्यादा काम नहीं करेगा, लेकिन कोई बात नहीं. बाद में, ज़्यादा फ़ंक्शन जोड़े जाएंगे.

ऐप्लिकेशन को डिप्लॉय करना

"Hello world" ऐप्लिकेशन को डिप्लॉय करने के लिए, आपको Cloud फ़ंक्शन को डिप्लॉय करना होगा. साथ ही, Google Cloud Console में Chat ऐप्लिकेशन को कॉन्फ़िगर करना होगा. इसके बाद, डिप्लॉयमेंट की पुष्टि करने के लिए, ऐप्लिकेशन को एक टेस्ट मैसेज भेजना होगा.

Cloud फ़ंक्शन डिप्लॉय करना

"Hello world" ऐप्लिकेशन के Cloud फ़ंक्शन को डिप्लॉय करने के लिए, यह कमांड डालें:

gcloud functions deploy app --trigger-http --security-level=secure-always --allow-unauthenticated --runtime nodejs14

प्रोसेस पूरी होने के बाद, आउटपुट कुछ ऐसा दिखना चाहिए:

availableMemoryMb: 256
buildId: 993b2ca9-2719-40af-86e4-42c8e4563a4b
buildName: projects/595241540133/locations/us-central1/builds/993b2ca9-2719-40af-86e4-42c8e4563a4b
entryPoint: app
httpsTrigger:
  securityLevel: SECURE_ALWAYS
  url: https://us-central1-poll-app-codelab.cloudfunctions.net/app
ingressSettings: ALLOW_ALL
labels:
  deployment-tool: cli-gcloud
name: projects/poll-app-codelab/locations/us-central1/functions/app
runtime: nodejs14
serviceAccountEmail: poll-app-codelab@appspot.gserviceaccount.com
sourceUploadUrl: https://storage.googleapis.com/gcf-upload-us-central1-66a01777-67f0-46d7-a941-079c24414822/94057943-2b7c-4b4c-9a21-bb3acffc84c6.zip
status: ACTIVE
timeout: 60s
updateTime: '2021-09-17T19:30:33.694Z'
versionId: '1'

httpsTrigger.url प्रॉपर्टी में, डिप्लॉय किए गए फ़ंक्शन का यूआरएल नोट करें. इसका इस्तेमाल अगले चरण में किया जाएगा.

ऐप्लिकेशन को कॉन्फ़िगर करना

ऐप्लिकेशन को कॉन्फ़िगर करने के लिए, Cloud Console में चैट कॉन्फ़िगरेशन पेज पर जाएं

  1. इस Chat ऐप्लिकेशन को Workspace ऐड-ऑन के तौर पर बनाएं से सही का निशान हटाएं. इसके बाद, पुष्टि करने के लिए बंद करें पर क्लिक करें.
  2. ऐप्लिकेशन का नाम में, "PollCodelab" डालें.
  3. अवतार यूआरएल में, https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png डालें.
  4. ब्यौरा में, "कोड लैब के लिए पोल ऐप्लिकेशन" डालें.
  5. सुविधाएं में जाकर, 1:1 मैसेज पाएं और स्पेस और ग्रुप बातचीत में शामिल हों को चुनें.
  6. कनेक्शन सेटिंग में जाकर, एचटीटीपी एंडपॉइंट यूआरएल चुनें. इसके बाद, Cloud फ़ंक्शन के लिए यूआरएल चिपकाएं (पिछले सेक्शन की httpsTrigger.url प्रॉपर्टी).
  7. अनुमतियां में जाकर, आपके डोमेन के चुनिंदा लोग और ग्रुप को चुनें. इसके बाद, अपना ईमेल पता डालें.
  8. सेव करें पर क्लिक करें.

अब इस ऐप्लिकेशन से मैसेज भेजे जा सकते हैं.

ऐप्लिकेशन को टेस्ट करना

आगे बढ़ने से पहले, यह पक्का करें कि ऐप्लिकेशन काम कर रहा हो. इसके लिए, उसे Google Chat के किसी स्पेस में जोड़ें.

  1. Google Chat पर जाएं.
  2. Chat के बगल में, + > ऐप्लिकेशन ढूंढें पर क्लिक करें.
  3. खोज बार में "PollCodelab" डालें.
  4. Chat पर क्लिक करें.
  5. ऐप्लिकेशन को मैसेज करने के लिए, "Hello" टाइप करें और Enter दबाएं.

ऐप्लिकेशन को 'नमस्ते' का छोटा सा मैसेज भेजना चाहिए.

अब बुनियादी ढांचा तैयार हो गया है. इसलिए, अब इसे ज़्यादा काम का बनाने का समय है!

5. पोल की सुविधाएं बनाना

ऐप्लिकेशन के काम करने के तरीके के बारे में खास जानकारी

ऐप्लिकेशन में दो मुख्य हिस्से होते हैं:

  1. यह एक स्लैश कमांड है. इससे पोल को कॉन्फ़िगर करने के लिए एक डायलॉग बॉक्स दिखता है.
  2. वोटिंग और नतीजे देखने के लिए इंटरैक्टिव कार्ड.

ऐप्लिकेशन को पोल के कॉन्फ़िगरेशन और नतीजों को सेव करने के लिए भी कुछ स्टेट की ज़रूरत होती है. इसके लिए, Firestore या किसी डेटाबेस का इस्तेमाल किया जा सकता है. इसके अलावा, ऐप्लिकेशन के मैसेज में ही स्थिति को सेव किया जा सकता है. इस ऐप्लिकेशन का इस्तेमाल, टीम के अनौपचारिक पोल के लिए किया जाता है. इसलिए, ऐप्लिकेशन के मैसेज में स्थिति को सेव करना, इस इस्तेमाल के उदाहरण के लिए सबसे सही तरीका है.

ऐप्लिकेशन के लिए डेटा मॉडल (टाइपस्क्रिप्ट में दिखाया गया है) यह है:

interface Poll {
  /* Question/topic of poll */
  topic: string;
  /** User that submitted the poll */
  author: {
    /** Unique resource name of user */
    name: string;
    /** Display name */
    displayName: string;
  };
  /** Available choices to present to users */
  choices: string[];
  /** Map of user ids to the index of their selected choice */
  votes: { [key: string]: number };
}

विषय या सवाल और विकल्पों की सूची के अलावा, राज्य में लेखक की आईडी और नाम के साथ-साथ रिकॉर्ड किए गए वोट भी शामिल होते हैं. उपयोगकर्ताओं को एक से ज़्यादा बार वोट करने से रोकने के लिए, वोटों को उपयोगकर्ता आईडी के मैप के तौर पर सेव किया जाता है. इसमें उपयोगकर्ता की चुनी गई पसंद का इंडेक्स होता है.

पोल चलाने के कई तरीके हैं. हालांकि, इस तरीके से किसी स्पेस में तुरंत पोल चलाने में मदद मिलती है.

पोल कॉन्फ़िगरेशन कमांड लागू करना

उपयोगकर्ताओं को पोल शुरू करने और उन्हें कॉन्फ़िगर करने की अनुमति देने के लिए, स्लैश कमांड सेट अप करें. इससे एक डायलॉग खुलता है. यह कई चरणों वाली प्रोसेस है:

  1. पोल शुरू करने वाली स्लैश कमांड रजिस्टर करें.
  2. पोल सेट अप करने वाला डायलॉग बॉक्स बनाएं.
  3. ऐप्लिकेशन को स्लैश कमांड पहचानने और उसे मैनेज करने की अनुमति दें.
  4. ऐसे इंटरैक्टिव कार्ड बनाएं जिनकी मदद से पोल में वोट किया जा सके.
  5. ऐसा कोड लागू करें जिससे ऐप्लिकेशन में पोल चलाए जा सकें.
  6. क्लाउड फ़ंक्शन को फिर से डिप्लॉय करें.

स्लैश कमांड रजिस्टर करना

स्लैश कमांड रजिस्टर करने के लिए, कंसोल में चैट कॉन्फ़िगरेशन पेज पर वापस जाएं (एपीआई और सेवाएं > डैशबोर्ड > Hangouts Chat API > कॉन्फ़िगरेशन).

  1. स्लैश कमांड में जाकर, नई स्लैश कमांड जोड़ें पर क्लिक करें.
  2. नाम में, "/poll" डालें
  3. कमांड आईडी में, "1" डालें
  4. ब्यौरा में जाकर, "पोल शुरू करें" डालें.
  5. डायलॉग बॉक्स खोलता है को चुनें.
  6. हो गया पर क्लिक करें.
  7. सेव करें पर क्लिक करें.

अब ऐप्लिकेशन, /poll कमांड को पहचानता है और एक डायलॉग बॉक्स खोलता है. इसके बाद, डायलॉग को कॉन्फ़िगर करते हैं.

डायलॉग के तौर पर कॉन्फ़िगरेशन फ़ॉर्म बनाना

स्लैश कमांड से एक डायलॉग बॉक्स खुलता है. इसमें पोल के विषय और संभावित विकल्पों को कॉन्फ़िगर किया जा सकता है. यहां दिए गए कॉन्टेंट के साथ config-form.js नाम की एक नई फ़ाइल बनाएं:

/** Upper bounds on number of choices to present. */
const MAX_NUM_OF_OPTIONS = 5;

/**
 * Build widget with instructions on how to use form.
 *
 * @returns {object} card widget
 */
function helpText() {
  return {
    textParagraph: {
      text: 'Enter the poll topic and up to 5 choices in the poll. Blank options will be omitted.',
    },
  };
}

/**
 * Build the text input for a choice.
 *
 * @param {number} index - Index to identify the choice
 * @param {string|undefined} value - Initial value to render (optional)
 * @returns {object} card widget
 */
function optionInput(index, value) {
  return {
    textInput: {
      label: `Option ${index + 1}`,
      type: 'SINGLE_LINE',
      name: `option${index}`,
      value: value || '',
    },
  };
}

/**
 * Build the text input for the poll topic.
 *
 * @param {string|undefined} topic - Initial value to render (optional)
 * @returns {object} card widget
 */
function topicInput(topic) {
  return {
    textInput: {
      label: 'Topic',
      type: 'MULTIPLE_LINE',
      name: 'topic',
      value: topic || '',
    },
  };
}

/**
 * Build the buttons/actions for the form.
 *
 * @returns {object} card widget
 */
function buttons() {
  return {
    buttonList: {
      buttons: [
        {
          text: 'Submit',
          onClick: {
            action: {
              function: 'start_poll',
            },
          },
        },
      ],
    },
  };
}

/**
 * Build the configuration form.
 *
 * @param {object} options - Initial state to render with form
 * @param {string|undefined} options.topic - Topic of poll (optional)
 * @param {string[]|undefined} options.choices - Text of choices to display to users (optional)
 * @returns {object} card
 */
function buildConfigurationForm(options) {
  const widgets = [];
  widgets.push(helpText());
  widgets.push(topicInput(options.topic));
  for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
    const choice = options?.choices?.[i];
    widgets.push(optionInput(i, choice));
  }
  widgets.push(buttons());

  // Assemble the card
  return {
    sections: [
      {
        widgets,
      },
    ],
  };
}

exports.MAX_NUM_OF_OPTIONS = MAX_NUM_OF_OPTIONS;
exports.buildConfigurationForm = buildConfigurationForm;

यह कोड, डायलॉग फ़ॉर्म जनरेट करता है. इसकी मदद से उपयोगकर्ता पोल सेट अप कर सकता है. यह एक ऐसा कॉन्स्टेंट भी एक्सपोर्ट करता है जिससे यह पता चलता है कि किसी सवाल में ज़्यादा से ज़्यादा कितने विकल्प हो सकते हैं. यूज़र इंटरफ़ेस (यूआई) मार्कअप को स्टेटलेस फ़ंक्शन में अलग से बनाने का तरीका सबसे सही है. इसमें पैरामीटर के तौर पर कोई भी स्टेट पास की जाती है. इससे कार्ड को दोबारा इस्तेमाल करना आसान हो जाता है. साथ ही, बाद में इस कार्ड को अलग-अलग कॉन्टेक्स्ट में रेंडर किया जाएगा.

इस तरीके से कार्ड को छोटी इकाइयों या कॉम्पोनेंट में भी बांटा जाता है. हालांकि, ऐसा करना ज़रूरी नहीं है, लेकिन यह तरीका सबसे सही है. इसकी वजह यह है कि जटिल इंटरफ़ेस बनाते समय, इसे पढ़ना और बनाए रखना आसान होता है.

यह टूल, पूरा JSON बनाता है. इसका सैंपल देखने के लिए, इसे कार्ड बिल्डर टूल में देखें.

स्लैश कमांड को हैंडल करना

ऐप्लिकेशन को भेजे जाने पर, स्लैश कमांड MESSAGE इवेंट के तौर पर दिखती हैं. index.js को अपडेट करें, ताकि MESSAGE इवेंट के ज़रिए स्लैश कमांड की मौजूदगी की जांच की जा सके और डायलॉग से जवाब दिया जा सके. index.js की जगह यह डालें:

const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');

/**
 * App entry point.
 */
exports.app = async (req, res) => {
  if (!(req.method === 'POST' && req.body)) {
      res.status(400).send('')
  }
  const event = req.body;
  let reply = {};
  // Dispatch slash and action events
  if (event.type === 'MESSAGE') {
    const message = event.message;
    if (message.slashCommand?.commandId === '1') {
      reply = showConfigurationForm(event);
    }
  } else if (event.type === 'CARD_CLICKED') {
    if (event.action?.actionMethodName === 'start_poll') {
      reply = await startPoll(event);
    }
  }
  res.json(reply);
}

/**
 * Handles the slash command to display the config form.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function showConfigurationForm(event) {
  // Seed the topic with any text after the slash command
  const topic = event.message?.argumentText?.trim();
  const dialog = buildConfigurationForm({
    topic,
    choices: [],
  });
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        dialog: {
          body: dialog,
        },
      },
    },
  };
}

/**
 * Handle the custom start_poll action.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function startPoll(event) {
  // Not fully implemented yet -- just close the dialog
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        actionStatus: {
          statusCode: 'OK',
          userFacingMessage: 'Poll started.',
        },
      },
    },
  }
}

अब /poll कमांड का इस्तेमाल करने पर, ऐप्लिकेशन में एक डायलॉग दिखेगा. Cloud Shell से Cloud Function को फिर से डिप्लॉय करके, इंटरैक्शन की जांच करें.

gcloud functions deploy app --trigger-http --security-level=secure-always

Cloud Functions को डिप्लॉय करने के बाद, ऐप्लिकेशन को /poll कमांड के साथ मैसेज भेजें. इससे स्लैश कमांड और डायलॉग की जांच की जा सकेगी. डायलॉग, कस्टम ऐक्शन start_poll के साथ CARD_CLICKED इवेंट भेजता है. इवेंट को अपडेट किए गए एंट्री पॉइंट में मैनेज किया जाता है. यहां यह startPoll तरीके को कॉल करता है. फ़िलहाल, startPoll तरीके का इस्तेमाल सिर्फ़ डायलॉग बॉक्स को बंद करने के लिए किया जाता है. अगले सेक्शन में, वोटिंग की सुविधा लागू की जाएगी और सभी हिस्सों को एक साथ जोड़ा जाएगा.

वोटिंग कार्ड लागू करना

ऐप्लिकेशन में वोटिंग की सुविधा लागू करने के लिए, सबसे पहले ऐसा इंटरैक्टिव कार्ड तय करें जो लोगों को वोट करने के लिए इंटरफ़ेस उपलब्ध कराता हो.

वोटिंग के इंटरफ़ेस को लागू करना

यहां दिए गए कॉन्टेंट के साथ vote-card.js नाम की फ़ाइल बनाएं:

/**
 * Creates a small progress bar to show percent of votes for an option. Since
 * width is limited, the percentage is scaled to 20 steps (5% increments).
 *
 * @param {number} voteCount - Number of votes for this option
 * @param {number} totalVotes - Total votes cast in the poll
 * @returns {string} Text snippet with bar and vote totals
 */
function progressBarText(voteCount, totalVotes) {
  if (voteCount === 0 || totalVotes === 0) {
    return '';
  }

  // For progress bar, calculate share of votes and scale it
  const percentage = (voteCount * 100) / totalVotes;
  const progress = Math.round((percentage / 100) * 20);
  return '▀'.repeat(progress);
}

/**
 * Builds a line in the card for a single choice, including
 * the current totals and voting action.
 *
 * @param {number} index - Index to identify the choice
 * @param {string|undefined} value - Text of the choice
 * @param {number} voteCount - Current number of votes cast for this item
 * @param {number} totalVotes - Total votes cast in poll
 * @param {string} state - Serialized state to send in events
 * @returns {object} card widget
 */
function choice(index, text, voteCount, totalVotes, state) {
  const progressBar = progressBarText(voteCount, totalVotes);
  return {
    keyValue: {
      bottomLabel: `${progressBar} ${voteCount}`,
      content: text,
      button: {
        textButton: {
          text: 'vote',
          onClick: {
            action: {
              actionMethodName: 'vote',
              parameters: [
                {
                  key: 'state',
                  value: state,
                },
                {
                  key: 'index',
                  value: index.toString(10),
                },
              ],
            },
          },
        },
      },
    },
  };
}

/**
 * Builds the card header including the question and author details.
 *
 * @param {string} topic - Topic of the poll
 * @param {string} author - Display name of user that created the poll
 * @returns {object} card widget
 */
function header(topic, author) {
  return {
    title: topic,
    subtitle: `Posted by ${author}`,
    imageUrl:
      'https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png',
    imageStyle: 'AVATAR',
  };
}

/**
 * Builds the configuration form.
 *
 * @param {object} poll - Current state of poll
 * @param {object} poll.author - User that submitted the poll
 * @param {string} poll.topic - Topic of poll
 * @param {string[]} poll.choices - Text of choices to display to users
 * @param {object} poll.votes - Map of cast votes keyed by user ids
 * @returns {object} card
 */
function buildVoteCard(poll) {
  const widgets = [];
  const state = JSON.stringify(poll);
  const totalVotes = Object.keys(poll.votes).length;

  for (let i = 0; i < poll.choices.length; ++i) {
    // Count votes for this choice
    const votes = Object.values(poll.votes).reduce((sum, vote) => {
      if (vote === i) {
        return sum + 1;
      }
      return sum;
    }, 0);
    widgets.push(choice(i, poll.choices[i], votes, totalVotes, state));
  }

  return {
    header: header(poll.topic, poll.author.displayName),
    sections: [
      {
        widgets,
      },
    ],
  };
}

exports.buildVoteCard = buildVoteCard;

इसे लागू करने का तरीका, डायलॉग के लिए इस्तेमाल किए गए तरीके जैसा ही है. हालांकि, इंटरैक्टिव कार्ड के लिए मार्कअप, डायलॉग के मार्कअप से थोड़ा अलग होता है. पहले की तरह, जनरेट किए गए JSON का सैंपल कार्ड बिल्डर टूल में देखा जा सकता है.

वोट करने की सुविधा लागू करना

वोटिंग कार्ड में, हर विकल्प के लिए एक बटन होता है. उस विकल्प का इंडेक्स और पोल की क्रम से लगाई गई स्थिति, बटन से जुड़ी होती है. ऐप्लिकेशन को CARD_CLICKED मिलता है. इसमें vote ऐक्शन के साथ-साथ, बटन से जुड़ा कोई भी डेटा पैरामीटर के तौर पर शामिल होता है.

index.js को इन फ़ील्ड के साथ अपडेट करें:

const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');
const { buildVoteCard } = require('./vote-card');

/**
 * App entry point.
 */
exports.app = async (req, res) => {
  if (!(req.method === 'POST' && req.body)) {
      res.status(400).send('')
  }
  const event = req.body;
  let reply = {};
  // Dispatch slash and action events
  if (event.type === 'MESSAGE') {
    const message = event.message;
    if (message.slashCommand?.commandId === '1') {
      reply = showConfigurationForm(event);
    }
  } else if (event.type === 'CARD_CLICKED') {
    if (event.action?.actionMethodName === 'start_poll') {
      reply = await startPoll(event);
    } else if (event.action?.actionMethodName === 'vote') {
        reply = recordVote(event);
    }
  }
  res.json(reply);
}

/**
 * Handles the slash command to display the config form.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function showConfigurationForm(event) {
  // Seed the topic with any text after the slash command
  const topic = event.message?.argumentText?.trim();
  const dialog = buildConfigurationForm({
    topic,
    choices: [],
  });
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        dialog: {
          body: dialog,
        },
      },
    },
  };
}

/**
 * Handle the custom start_poll action.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function startPoll(event) {
  // Not fully implemented yet -- just close the dialog
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        actionStatus: {
          statusCode: 'OK',
          userFacingMessage: 'Poll started.',
        },
      },
    },
  }
}

/**
 * Handle the custom vote action. Updates the state to record
 * the user's vote then rerenders the card.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function recordVote(event) {
  const parameters = event.common?.parameters;

  const choice = parseInt(parameters['index']);
  const userId = event.user.name;
  const state = JSON.parse(parameters['state']);

  // Add or update the user's selected option
  state.votes[userId] = choice;

  const card = buildVoteCard(state);
  return {
    thread: event.message.thread,
    actionResponse: {
      type: 'UPDATE_MESSAGE',
    },
    cards: [card],
  }
}

recordVote तरीके से, सेव की गई स्थिति को पार्स किया जाता है और उसे उपयोगकर्ता के वोट के साथ अपडेट किया जाता है. इसके बाद, कार्ड को फिर से रेंडर किया जाता है. पोल के नतीजों को क्रम से लगाया जाता है और हर बार अपडेट होने पर, उन्हें कार्ड के साथ सेव किया जाता है.

अलग-अलग हिस्सों को एक साथ जोड़ना

ऐप्लिकेशन करीब-करीब तैयार हो गया है. वोटिंग के साथ स्लैश कमांड लागू करने के बाद, सिर्फ़ startPoll तरीके को पूरा करना बाकी है.

हालांकि, इसमें एक पेंच है.

पोल का कॉन्फ़िगरेशन सबमिट करने के बाद, ऐप्लिकेशन को ये दो कार्रवाइयां करनी होंगी:

  1. डायलॉग बॉक्स बंद करें.
  2. वोटिंग कार्ड के साथ स्पेस में नया मैसेज पोस्ट करें.

माफ़ करें, एचटीटीपी अनुरोध का सीधा जवाब सिर्फ़ एक बार दिया जा सकता है. साथ ही, यह ज़रूरी है कि यह जवाब सबसे पहले दिया जाए. वोट कार्ड पोस्ट करने के लिए, ऐप्लिकेशन को Chat API का इस्तेमाल करके, एसिंक्रोनस तरीके से नया मैसेज बनाना होगा.

क्लाइंट लाइब्रेरी जोड़ना

ऐप्लिकेशन की डिपेंडेंसी को अपडेट करने के लिए, यहां दिया गया कमांड चलाएं. इससे Node.js के लिए Google API क्लाइंट शामिल किया जा सकेगा.

npm install --save googleapis

पोल शुरू करें

index.js को यहां दिए गए फ़ाइनल वर्शन में अपडेट करें:

const { buildConfigurationForm, MAX_NUM_OF_OPTIONS } = require('./config-form');
const { buildVoteCard } = require('./vote-card');
const {google} = require('googleapis');

/**
 * App entry point.
 */
exports.app = async (req, res) => {
  if (!(req.method === 'POST' && req.body)) {
      res.status(400).send('')
  }
  const event = req.body;
  let reply = {};
  // Dispatch slash and action events
  if (event.type === 'MESSAGE') {
    const message = event.message;
    if (message.slashCommand?.commandId === '1') {
      reply = showConfigurationForm(event);
    }
  } else if (event.type === 'CARD_CLICKED') {
    if (event.action?.actionMethodName === 'start_poll') {
      reply = await startPoll(event);
    } else if (event.action?.actionMethodName === 'vote') {
        reply = recordVote(event);
    }
  }
  res.json(reply);
}

/**
 * Handles the slash command to display the config form.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function showConfigurationForm(event) {
  // Seed the topic with any text after the slash command
  const topic = event.message?.argumentText?.trim();
  const dialog = buildConfigurationForm({
    topic,
    choices: [],
  });
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        dialog: {
          body: dialog,
        },
      },
    },
  };
}

/**
 * Handle the custom start_poll action.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
async function startPoll(event) {
  // Get the form values
  const formValues = event.common?.formInputs;
  const topic = formValues?.['topic']?.stringInputs.value[0]?.trim();
  const choices = [];
  for (let i = 0; i < MAX_NUM_OF_OPTIONS; ++i) {
    const choice = formValues?.[`option${i}`]?.stringInputs.value[0]?.trim();
    if (choice) {
      choices.push(choice);
    }
  }

  if (!topic || choices.length === 0) {
    // Incomplete form submitted, rerender
    const dialog = buildConfigurationForm({
      topic,
      choices,
    });
    return {
      actionResponse: {
        type: 'DIALOG',
        dialogAction: {
          dialog: {
            body: dialog,
          },
        },
      },
    };
  }

  // Valid configuration, build the voting card to display
  // in the space
  const pollCard = buildVoteCard({
    topic: topic,
    author: event.user,
    choices: choices,
    votes: {},
  });
  const message = {
    cards: [pollCard],
  };
  const request = {
    parent: event.space.name,
    requestBody: message,
  };
  // Use default credentials (service account)
  const credentials = new google.auth.GoogleAuth({
    scopes: ['https://www.googleapis.com/auth/chat.bot'],
  });
  const chatApi = google.chat({
    version: 'v1',
    auth: credentials,
  });
  await chatApi.spaces.messages.create(request);

  // Close dialog
  return {
    actionResponse: {
      type: 'DIALOG',
      dialogAction: {
        actionStatus: {
          statusCode: 'OK',
          userFacingMessage: 'Poll started.',
        },
      },
    },
  };
}

/**
 * Handle the custom vote action. Updates the state to record
 * the user's vote then rerenders the card.
 *
 * @param {object} event - chat event
 * @returns {object} Response to send back to Chat
 */
function recordVote(event) {
  const parameters = event.common?.parameters;

  const choice = parseInt(parameters['index']);
  const userId = event.user.name;
  const state = JSON.parse(parameters['state']);

  // Add or update the user's selected option
  state.votes[userId] = choice;

  const card = buildVoteCard(state);
  return {
    thread: event.message.thread,
    actionResponse: {
      type: 'UPDATE_MESSAGE',
    },
    cards: [card],
  }
}

फ़ंक्शन को फिर से डिप्लॉय करें:

gcloud functions deploy app --trigger-http --security-level=secure-always

अब आपको ऐप्लिकेशन की सभी सुविधाओं का इस्तेमाल करने का विकल्प मिलेगा. /poll कमांड का इस्तेमाल करके देखें. इसके लिए, कोई सवाल और उसके कुछ विकल्प दें. सबमिट करने के बाद, पोल कार्ड दिखता है.

वोट करें और देखें कि क्या होता है.

खुद से पोल करने का कोई फ़ायदा नहीं होता. इसलिए, अपने कुछ दोस्तों या सहकर्मियों को न्योता दें, ताकि वे इसे आज़मा सकें!

6. बधाई हो

बधाई हो! आपने Cloud Functions का इस्तेमाल करके, Google Chat ऐप्लिकेशन को बना लिया है और उसे डिप्लॉय कर दिया है. कोडलैब में, ऐप्लिकेशन बनाने से जुड़े कई मुख्य कॉन्सेप्ट शामिल किए गए हैं. हालांकि, एक्सप्लोर करने के लिए और भी बहुत कुछ है. यहां दिए गए संसाधन देखें. साथ ही, अतिरिक्त शुल्क से बचने के लिए अपने प्रोजेक्ट को बंद करना न भूलें.

अन्य गतिविधियां

अगर आपको Chat प्लैटफ़ॉर्म और इस ऐप्लिकेशन के बारे में ज़्यादा जानना है, तो यहां दिए गए कुछ तरीके आज़माएं:

  • ऐप्लिकेशन को @ टैग करने पर क्या होता है? ऐप्लिकेशन को अपडेट करके देखें.
  • कार्ड में पोल की स्थिति को क्रम से लगाने पर, छोटी जगहों के लिए यह ठीक है. हालांकि, इसकी कुछ सीमाएं हैं. किसी बेहतर विकल्प पर स्विच करके देखें.
  • अगर लेखक को पोल में बदलाव करना हो या नए वोट लेना बंद करना हो, तो क्या होगा? उन सुविधाओं को कैसे लागू किया जाएगा?
  • ऐप्लिकेशन एंडपॉइंट को अब तक सुरक्षित नहीं किया गया है. पुष्टि करने की सुविधा जोड़ें, ताकि यह पक्का किया जा सके कि अनुरोध Google Chat से आ रहे हैं.

ऐप्लिकेशन को बेहतर बनाने के लिए, यहां कुछ तरीके दिए गए हैं. इनका इस्तेमाल करके, अपनी कल्पना को उड़ान दें और इस सुविधा का आनंद लें!

व्यवस्थित करें

इस ट्यूटोरियल में इस्तेमाल किए गए संसाधनों के लिए, अपने Google Cloud Platform खाते से शुल्क न लिए जाने के लिए:

  • Cloud Console में, संसाधन मैनेज करें पेज पर जाएं. सबसे ऊपर बाएं कोने में मौजूद, मेन्यू मेन्यू आइकॉन > IAM और एडमिन > संसाधन मैनेज करें पर क्लिक करें.
  1. प्रोजेक्ट की सूची में, अपना प्रोजेक्ट चुनें. इसके बाद, मिटाएं पर क्लिक करें.
  2. डायलॉग बॉक्स में, प्रोजेक्ट आईडी टाइप करें. इसके बाद, प्रोजेक्ट मिटाने के लिए बंद करें पर क्लिक करें.

ज़्यादा जानें

Chat ऐप्लिकेशन डेवलप करने के बारे में ज़्यादा जानने के लिए, यह लेख पढ़ें:

Google Cloud Console में डेवलपमेंट करने के बारे में ज़्यादा जानने के लिए, यह लेख पढ़ें: