با Node.js یک برنامه نظرسنجی تعاملی برای Google Chat بسازید، با Node.js یک برنامه نظرسنجی تعاملی برای Google Chat بسازید.

1. مقدمه

برنامه‌های گپ Google، خدمات و منابع شما را مستقیماً به چت Google وارد می‌کنند و به کاربران اجازه می‌دهند بدون ترک مکالمه، اطلاعات دریافت کنند و سریع اقدام کنند.

در این لبه کد، نحوه ساخت و استقرار یک برنامه نظرسنجی با استفاده از Node.js و Cloud Functions را خواهید آموخت.

برنامه ای که نظرسنجی را پست می کند و از اعضای فضایی می پرسد که آیا برنامه ها عالی هستند یا خیر و با یک پیام کارت تعاملی رای جمع آوری می کند.

چیزی که یاد خواهید گرفت

  • از Cloud Shell استفاده کنید
  • استقرار در توابع ابری
  • دریافت ورودی کاربر با دستورات اسلش و دیالوگ ها
  • کارت های تعاملی ایجاد کنید

2. راه اندازی و الزامات

یک پروژه Google Cloud ایجاد کنید، سپس APIها و خدماتی را که برنامه 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، روی Activate 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 اجازه دهید تا یک تماس API GCP برقرار کند، روی تأیید کلیک کنید.

    خروجی فرمان
    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].
    

با تکمیل این کد لبه، از عملیات خط فرمان و ویرایش فایل ها استفاده خواهید کرد. برای ویرایش فایل، با کلیک روی Open Editor در سمت راست نوار ابزار Cloud Shell، می‌توانید با ویرایشگر کد داخلی Cloud Shell، Cloud Shell Editor کار کنید. ویرایشگرهای محبوبی مانند Vim و Emacs نیز در Cloud Shell موجود هستند.

3. Cloud Functions، Cloud Build و Google Chat APIs را فعال کنید

از Cloud Shell، این APIها و خدمات را فعال کنید:

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

این عملیات ممکن است چند لحظه طول بکشد.

پس از تکمیل، یک پیام موفقیت آمیز مشابه این ظاهر می شود:

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

4. برنامه چت اولیه را ایجاد کنید

پروژه را راه اندازی کنید

برای شروع، یک برنامه ساده «Hello world» را ایجاد و اجرا خواهید کرد. برنامه‌های چت سرویس‌های وب هستند که به درخواست‌های https پاسخ می‌دهند و با یک بار JSON پاسخ می‌دهند. برای این برنامه، از Node.js و Cloud Functions استفاده خواهید کرد.

در Cloud Shell ، یک دایرکتوری جدید به نام poll-app ایجاد کنید و به آن بروید:

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

تمام کارهای باقی مانده برای Codelab و فایل هایی که ایجاد می کنید در این دایرکتوری خواهند بود.

پروژه Node.js را راه اندازی کنید:

npm init

NPM چندین سوال در مورد پیکربندی پروژه می پرسد، مانند نام و نسخه. برای هر سوال، ENTER فشار دهید تا مقادیر پیش فرض را بپذیرید. نقطه ورودی پیش فرض فایلی به نام index.js است که در ادامه آن را ایجاد خواهیم کرد.

پشتیبان برنامه چت را ایجاد کنید

زمان شروع ایجاد برنامه است. فایلی به نام 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 پیکربندی می‌کنید و یک پیام آزمایشی برای تأیید استقرار به برنامه ارسال می‌کنید.

کارکرد Cloud را اجرا کنید

برای استقرار عملکرد ابری برنامه "Hello world"، دستور زیر را وارد کنید:

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'

به URL تابع مستقر در ویژگی httpsTrigger.url توجه کنید. در مرحله بعد از آن استفاده خواهید کرد.

برنامه را پیکربندی کنید

برای پیکربندی برنامه، به صفحه تنظیمات چت در Cloud Console بروید

  1. Build this Chat app به عنوان یک افزونه Workspace را پاک کنید و برای تأیید بر روی DISABLE کلیک کنید.
  2. در نام برنامه ، "PollCodelab" را وارد کنید.
  3. در URL آواتار ، https://raw.githubusercontent.com/google/material-design-icons/master/png/social/poll/materialicons/24dp/2x/baseline_poll_black_24dp.png را وارد کنید.
  4. در توضیحات ، "Poll app for codelab" را وارد کنید.
  5. در بخش عملکرد ، دریافت پیام های 1:1 و پیوستن به فضاها و مکالمات گروهی را انتخاب کنید.
  6. در قسمت تنظیمات اتصال ، URL نقطه پایانی HTTP را انتخاب کنید و URL را برای تابع Cloud (ویژگی httpsTrigger.url از آخرین بخش) جای‌گذاری کنید.
  7. در قسمت مجوزها ، افراد و گروه‌های خاص را در دامنه خود انتخاب کنید و آدرس ایمیل خود را وارد کنید.
  8. ذخیره را کلیک کنید.

اکنون برنامه آماده ارسال پیام است.

برنامه را تست کنید

قبل از ادامه کار، با افزودن آن به فضایی در Google Chat، بررسی کنید که برنامه کار می‌کند.

  1. به Google Chat بروید.
  2. در کنار Chat، روی + > Find apps کلیک کنید.
  3. "PollCodelab" را در جستجو وارد کنید.
  4. روی چت کلیک کنید.
  5. برای پیام دادن به برنامه، "Hello" را تایپ کنید و اینتر را فشار دهید.

برنامه باید با یک پیام سلام کوتاه پاسخ دهد.

اکنون که یک اسکلت اساسی در جای خود وجود دارد، وقت آن است که آن را به چیزی مفیدتر تبدیل کنید!

5. ویژگی های نظرسنجی را بسازید

یک نمای کلی از نحوه عملکرد برنامه

این اپلیکیشن از دو بخش اصلی تشکیل شده است:

  1. یک دستور اسلش که یک دیالوگ برای پیکربندی نظرسنجی نمایش می دهد.
  2. کارت تعاملی برای رای دادن و مشاهده نتایج.

این برنامه همچنین برای ذخیره تنظیمات نظرسنجی و نتایج به حالتی نیاز دارد. این کار را می توان با Firestore یا هر پایگاه داده ای انجام داد، یا وضعیت را می توان در پیام های برنامه ذخیره کرد. از آنجایی که این برنامه برای نظرسنجی های سریع غیررسمی یک تیم در نظر گرفته شده است، ذخیره وضعیت در پیام های برنامه برای این مورد عالی کار می کند.

مدل داده برای برنامه (بیان شده در Typescript) این است:

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. باز استقرار تابع ابر.

دستور اسلش را ثبت کنید

برای ثبت دستور اسلش، به صفحه پیکربندی چت در کنسول ( APIs & Services > Dashboard > Hangouts Chat API > Configuration ) برگردید.

  1. در زیر دستورات اسلش ، روی افزودن دستور اسلش جدید کلیک کنید.
  2. در Name "/poll" را وارد کنید
  3. در شناسه فرمان ، "1" را وارد کنید
  4. در توضیحات ، «شروع یک نظرسنجی» را وارد کنید.
  5. باز کردن یک گفتگو را انتخاب کنید.
  6. روی Done کلیک کنید.
  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;

این کد فرم گفتگو را ایجاد می کند که به کاربر امکان می دهد نظرسنجی را تنظیم کند. همچنین برای حداکثر تعداد گزینه هایی که یک سوال می تواند داشته باشد یک ثابت صادر می کند. جداسازی ساخت نشانه‌گذاری UI در توابع بدون حالت با هر حالتی که به عنوان پارامتر ارسال می‌شود، تمرین خوبی است. استفاده مجدد را تسهیل می کند و بعداً این کارت در زمینه های مختلف ارائه می شود.

این پیاده سازی همچنین کارت را به واحدها یا اجزای کوچکتر تجزیه می کند. اگرچه این تکنیک مورد نیاز نیست، اما بهترین روش است زیرا در هنگام ساخت رابط های پیچیده، خواناتر و قابل نگهداری تر است.

برای دیدن نمونه‌ای از JSON کاملی که می‌سازد، آن را در ابزار Card Builder مشاهده کنید.

دستور اسلش را مدیریت کنید

دستورات اسلش هنگام ارسال به برنامه به صورت رویداد 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 از Cloud Shell، تعامل را آزمایش کنید.

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

پس از استقرار Cloud Function، دستور /poll را به برنامه پیام دهید تا دستور اسلش و گفتگو را آزمایش کنید. گفتگو یک رویداد CARD_CLICKED را با اقدام سفارشی start_poll ارسال می کند. رویداد در نقطه ورودی به روز شده ای که روش 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 Builder مشاهده کنید.

عمل رای را اجرا کنید

کارت رای گیری شامل یک دکمه برای هر انتخاب است. نمایه آن انتخاب به همراه وضعیت سریالی نظرسنجی به دکمه متصل شده است. برنامه یک 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. با کارت رای دادن پیام جدیدی به فضا ارسال کنید.

متأسفانه، پاسخ مستقیم به درخواست HTTP فقط می تواند یک مورد را انجام دهد و آن باید اولین مورد باشد. برای ارسال کارت رأی، برنامه باید از Chat API برای ایجاد پیام جدید به صورت ناهمزمان استفاده کند.

کتابخانه مشتری را اضافه کنید

دستور زیر را برای به روز رسانی وابستگی های برنامه اجرا کنید تا شامل سرویس گیرنده Google API برای Node.js شود.

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. تبریک می گویم

تبریک می گویم! شما با موفقیت یک برنامه چت Google را با استفاده از توابع Cloud ساخته و راه اندازی کرده اید. در حالی که نرم افزار کد بسیاری از مفاهیم اصلی ساخت یک برنامه را پوشش می دهد، چیزهای بیشتری برای کشف وجود دارد. منابع زیر را ببینید و فراموش نکنید که پروژه خود را تمیز کنید تا از هزینه های اضافی جلوگیری کنید.

فعالیت های اضافی

اگر می‌خواهید پلتفرم چت و این برنامه را عمیق‌تر کاوش کنید، در اینجا چند چیز وجود دارد که می‌توانید خودتان امتحان کنید:

  • چه اتفاقی می‌افتد وقتی که @ برنامه را ذکر کنید؟ برای بهبود رفتار، برنامه را به‌روزرسانی کنید.
  • سریال کردن وضعیت نظرسنجی در کارت برای فضاهای کوچک خوب است، اما محدودیت هایی دارد. سعی کنید به گزینه بهتری بروید.
  • اگر نویسنده بخواهد نظرسنجی را ویرایش کند، یا از گرفتن رای جدید جلوگیری کند، چه؟ چگونه آن ویژگی ها را پیاده سازی می کنید؟
  • نقطه پایانی برنامه هنوز ایمن نشده است. برای اطمینان از اینکه درخواست‌ها از Google Chat می‌آیند، مقداری تأیید اضافه کنید.

اینها تنها چند روش مختلف برای بهبود برنامه هستند. از آن لذت ببرید و از تخیل خود استفاده کنید!

پاک کن

برای جلوگیری از تحمیل هزینه به حساب Google Cloud Platform برای منابع استفاده شده در این آموزش:

  • در Cloud Console، به صفحه مدیریت منابع بروید. در گوشه بالا سمت چپ، روی Menu کلیک کنید نماد منو > IAM & Admin > مدیریت منابع .
  1. در لیست پروژه، پروژه خود را انتخاب کنید و سپس روی حذف کلیک کنید.
  2. در گفتگو، ID پروژه را تایپ کنید و سپس بر روی Shut down کلیک کنید تا پروژه حذف شود.

بیشتر بدانید

برای اطلاعات بیشتر در مورد توسعه برنامه‌های چت، رجوع کنید به:

برای اطلاعات بیشتر در مورد توسعه در Google Cloud Console، ببینید: