কোডিং লেভেল : ইন্টারমিডিয়েট
সময়কাল : 45 মিনিট
প্রকল্পের ধরন : Google Workspace অ্যাড-অন
উদ্দেশ্য
- বুঝুন সমাধান কি করে।
- সমাধানের মধ্যে অ্যাপস স্ক্রিপ্ট পরিষেবাগুলি কী করে তা বুঝুন।
- পরিবেশ স্থাপন করুন।
- স্ক্রিপ্ট সেট আপ করুন।
- স্ক্রিপ্ট চালান।
এই সমাধান সম্পর্কে
আপনি Google Workspace-এ কাজ করার সময় আপনার প্রতিষ্ঠানে যাদের সাথে সহযোগিতা করেন তাদের সম্পর্কে ইমেল, ফোন নম্বর এবং বিভাগের মতো তথ্য দেখান। আপনি Gmail বার্তাগুলির প্রতিক্রিয়া, একটি Google ড্রাইভ ফাইল সম্পাদনা করার সময়, বা Google ক্যালেন্ডার ইভেন্টগুলি দেখার সময় এই তথ্যটি দেখতে পারেন৷
এটা কিভাবে কাজ করে
স্ক্রিপ্টটি সক্রিয় বার্তা, ফাইল বা ইভেন্ট থেকে ইমেল ঠিকানা পায়। প্রেক্ষাপটের উপর নির্ভর করে, এতে Gmail বার্তা প্রাপক, ড্রাইভ ফাইল সম্পাদক এবং ক্যালেন্ডার ইভেন্টে অংশগ্রহণকারীরা অন্তর্ভুক্ত থাকতে পারে। স্ক্রিপ্ট শুধুমাত্র আপনার প্রতিষ্ঠানের ইমেল ঠিকানার জন্য তথ্য দেখায়.
অ্যাপস স্ক্রিপ্ট পরিষেবা
এই সমাধানটি নিম্নলিখিত পরিষেবাগুলি ব্যবহার করে:
- অ্যাডমিন SDK ডিরেক্টরি উন্নত পরিষেবা - ডিরেক্টরি API ব্যবহার করে লোকেদের জন্য অনুসন্ধান করে৷
- বেস পরিষেবা - ইমেল ঠিকানাগুলি ফিল্টার করতে এবং অনুসন্ধান ফলাফলে বর্তমান ব্যবহারকারীকে না দেখানোর জন্য সেশন ক্লাস ব্যবহার করে।
- ক্যাশে পরিষেবা - ডিরেক্টরি API থেকে একক ব্যক্তির সন্ধান করার সময় প্রথমে ক্যাশে অনুসন্ধান করে।
- ক্যালেন্ডার পরিষেবা - যদি প্রসঙ্গটি একটি ক্যালেন্ডার ইভেন্ট হয়, তাহলে সক্রিয় ইভেন্ট থেকে ইমেল ঠিকানা পায়।
- কার্ড পরিষেবা - অ্যাড-অনের ব্যবহারকারী ইন্টারফেস তৈরি করে।
- ড্রাইভ পরিষেবা - যদি প্রসঙ্গটি একটি ড্রাইভ ফাইল হয়, ব্যবহারকারীর সক্রিয় ফাইলে দেখার অনুমতি থাকলে সহযোগীদের ইমেল ঠিকানাগুলি পায়৷
- Gmail পরিষেবা - যদি প্রসঙ্গটি একটি Gmail বার্তা হয়, তাহলে সক্রিয় Gmail বার্তার To, Cc এবং From ক্ষেত্রগুলি থেকে ইমেল ঠিকানাগুলি পায়৷
পূর্বশর্ত
- ইন্টারনেট অ্যাক্সেস সহ একটি ওয়েব ব্রাউজার।
- একটি Google Workspace অ্যাকাউন্ট (আপনার অ্যাডমিনিস্ট্রেটরের অনুমোদনের প্রয়োজন হতে পারে)।
- একটি Google ক্লাউড প্রকল্প ।
আপনার পরিবেশ সেট আপ করুন
Google ক্লাউড কনসোলে আপনার ক্লাউড প্রকল্প খুলুন
যদি এটি ইতিমধ্যে খোলা না থাকে, তাহলে ক্লাউড প্রকল্পটি খুলুন যা আপনি এই নমুনার জন্য ব্যবহার করতে চান:
- Google ক্লাউড কনসোলে, একটি প্রকল্প নির্বাচন করুন পৃষ্ঠাতে যান।
- আপনি যে Google ক্লাউড প্রকল্পটি ব্যবহার করতে চান সেটি নির্বাচন করুন। অথবা, প্রকল্প তৈরি করুন ক্লিক করুন এবং অন-স্ক্রীন নির্দেশাবলী অনুসরণ করুন। আপনি যদি একটি Google ক্লাউড প্রকল্প তৈরি করেন, তাহলে আপনাকে প্রকল্পটির জন্য বিলিং চালু করতে হতে পারে।
অ্যাডমিন SDK API চালু করুন
এই কুইকস্টার্ট অ্যাডমিন SDK API ডিরেক্টরির উন্নত পরিষেবা ব্যবহার করে, যা অ্যাডমিন SDK API অ্যাক্সেস করে।
Google API ব্যবহার করার আগে, আপনাকে একটি Google ক্লাউড প্রকল্পে সেগুলি চালু করতে হবে। আপনি একটি একক Google ক্লাউড প্রকল্পে এক বা একাধিক API চালু করতে পারেন৷আপনার ক্লাউড প্রকল্পে, অ্যাডমিন SDK API চালু করুন।
OAuth সম্মতি স্ক্রীন কনফিগার করুন
Google Workspace অ্যাড-অনগুলির জন্য একটি সম্মতি স্ক্রিন কনফিগারেশন প্রয়োজন। আপনার অ্যাড-অনের OAuth সম্মতি স্ক্রীন কনফিগার করলে তা নির্ধারণ করে যে Google ব্যবহারকারীদের কাছে কী প্রদর্শন করে।
- Google ক্লাউড কনসোলে, মেনু > API এবং পরিষেবা > OAuth সম্মতি স্ক্রীনে যান।
- ব্যবহারকারীর প্রকারের জন্য অভ্যন্তরীণ নির্বাচন করুন, তারপরে তৈরি করুন ক্লিক করুন।
- অ্যাপ রেজিস্ট্রেশন ফর্মটি পূরণ করুন, তারপর সংরক্ষণ করুন এবং চালিয়ে যান-এ ক্লিক করুন।
আপাতত, আপনি স্কোপ যোগ করা এড়িয়ে যেতে পারেন এবং সেভ এবং কন্টিনিউ এ ক্লিক করতে পারেন। ভবিষ্যতে, যখন আপনি আপনার Google Workspace সংস্থার বাইরে ব্যবহারের জন্য একটি অ্যাপ তৈরি করবেন, তখন আপনাকে অবশ্যই ব্যবহারকারীর ধরন পরিবর্তন করে External তে পরিবর্তন করতে হবে এবং তারপরে, আপনার অ্যাপের প্রয়োজনীয় অনুমোদনের সুযোগ যোগ করতে হবে।
- আপনার অ্যাপ নিবন্ধন সারাংশ পর্যালোচনা করুন. পরিবর্তন করতে, সম্পাদনা ক্লিক করুন। অ্যাপ নিবন্ধন ঠিক আছে বলে মনে হলে, ড্যাশবোর্ডে ফিরে যান ক্লিক করুন।
স্ক্রিপ্ট সেট আপ করুন
অ্যাপস স্ক্রিপ্ট প্রকল্প তৈরি করুন
টিম তালিকা অ্যাপস স্ক্রিপ্ট প্রকল্প খুলতে নিম্নলিখিত বোতামে ক্লিক করুন.
প্রকল্প খুলুনওভারভিউ
ক্লিক করুন।ওভারভিউ পৃষ্ঠায়, একটি অনুলিপি তৈরি করুন ক্লিক করুন .
ক্লাউড প্রকল্প নম্বর কপি করুন
- Google ক্লাউড কনসোলে, মেনু > IAM & Admin > সেটিংসে যান।
- প্রকল্প নম্বর ক্ষেত্রে, মানটি অনুলিপি করুন।
অ্যাপস স্ক্রিপ্ট প্রকল্পের ক্লাউড প্রকল্প সেট করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, প্রকল্প সেটিংস ক্লিক করুন .
- Google ক্লাউড প্ল্যাটফর্ম (GCP) প্রকল্পের অধীনে, প্রকল্প পরিবর্তন করুন ক্লিক করুন।
- GCP প্রকল্প নম্বরে , Google ক্লাউড প্রকল্প নম্বর পেস্ট করুন।
- সেট প্রকল্প ক্লিক করুন.
একটি পরীক্ষা স্থাপনা ইনস্টল করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, সম্পাদক ক্লিক করুন।
-
Code.gs
ফাইলটি খুলুন এবং রান এ ক্লিক করুন। অনুরোধ করা হলে, স্ক্রিপ্ট অনুমোদন করুন. - Deploy > Test deployments এ ক্লিক করুন।
- ইনস্টল > সম্পন্ন ক্লিক করুন।
স্ক্রিপ্ট চালান
- একটি Gmail বার্তা, ক্যালেন্ডার ইভেন্ট বা ড্রাইভ ফাইল খুলুন।
- ডান সাইডবারে, টিম লিস্ট অ্যাড-অন খুলুন।
- অনুরোধ করা হলে, অ্যাড-অন অনুমোদন করুন।
- অ্যাড-অন টিমের সদস্যদের সম্পর্কে তথ্য দেখায় বা নির্দেশ করে যে বার্তা, ইভেন্ট বা ফাইলে কোনও দলের সদস্য নেই।
- দলের সদস্যদের খুঁজে পেতে, লোকেদের জন্য অনুসন্ধানে ক্লিক করুন এবং একটি নাম বা ইমেল ঠিকানা লিখুন৷ অনুসন্ধানে ক্লিক করুন।
কোড পর্যালোচনা করুন
এই সমাধানের জন্য অ্যাপস স্ক্রিপ্ট কোড পর্যালোচনা করতে, নীচের উৎস কোড দেখুন ক্লিক করুন:
সোর্স কোড দেখুন
Code.gs
// Copyright 2022 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Sample Google Workspace Add-on that displays profile information about people // the user is collaborating with. Collaborators are based on the context -- // recipients of a gmail message, Drive file ACLs, or event attendees. // // Profile information is from the Directory API in the Admin SDK. As a result, // the add-on only shows information for email addresses in the same domain // as as the current user. Different strategies can be used for other use cases, // such as integration with a CRM where the focus may be on external email // addresses/customers. // See https://github.com/contributorpw/lodashgs var _ = LodashGS.load(); /** * Renders the home page for the add-on. Used in all host apps when * no context selected. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onHomePage(event) { var card = buildSearchCard_(); return [card]; } /** * Renders the contextual interface for a Gmail message. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onGmailMessageSelected(event) { var emails = extractEmailsFromMessage_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current message."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a calendar event. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onCalendarEventOpen(event) { var emails = extractEmailsFromCalendarEvent_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current event."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a selected Drive file. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onDriveItemsSelected(event) { // For demo, only allow single select on files. if (event.drive.selectedItems.length != 1) { var message = "To view team members collaborating on a file, select one file only."; var card = buildSearchCard_(message); return [card]; } var selectedItem = event.drive.selectedItems[0]; if (!selectedItem.addonHasFileScopePermission) { // Need file access to read ACL, ask user to authorize. var authorizeFilesAction = CardService.newAction() .setFunctionName("onAuthorizeDriveFiles") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({id: selectedItem.id}); var authorizationMessage = CardService.newTextParagraph() .setText("To view the people on your team the file is shared with, click *Authorize* to grant access."); var authorizeButton = CardService.newTextButton() .setText("Authorize") .setOnClickAction(authorizeFilesAction); var card = CardService.newCardBuilder() .addSection(CardService.newCardSection() .addWidget(authorizationMessage) .addWidget(authorizeButton)) .build(); return [card]; } // Have access, extract ACLs to find co-workers var emails = extractEmailsFromDrivePermissions_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current file."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Handles the click for requesting drive file access. * * @param {Object} event - current add-on event * @return {ActionResponse} Request to authorize access to a drive item */ function onAuthorizeDriveFiles(event) { var id = event.parameters.id; return CardService.newDriveItemsSelectedActionResponseBuilder() .requestFileScope(id) .build(); } /** * Handles the user search request. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onSearch(event) { if (!event.formInputs || !event.formInputs.query) { var notification = CardService.newNotification() .setText("Enter a query before searching."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var query = event.formInputs.query[0]; var people = queryPeople_(query); if (!people || people.length == 0) { var notification = CardService.newNotification().setText("No people found."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var card = buildTeamListCard_(people); var navigation = CardService.newNavigation().pushCard(card); return CardService.newActionResponseBuilder() .setNavigation(navigation) .build(); } /** * Handles the drill down to view detailed information about a person. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onShowPersonDetails(event) { var person = fetchPerson_(event.parameters.email); var card = buildPersonDetailsCard_(person); return [card] } /** * Builds a card for displaying detailed information about a team member. Currently only shows * a small subset of available information for demo purposes. * * @param {Object} person - User object from the Directory API * @return {Card} Card to display */ function buildPersonDetailsCard_(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var cardHeader = CardService.newCardHeader() .setImageUrl(photoUrl) .setImageStyle(CardService.ImageStyle.CIRCLE) .setTitle(person.name.fullName) if (person.organizations && person.organizations.length) { cardHeader.setSubtitle(person.organizations[0].title); } var section = CardService.newCardSection(); if (person.emails) { person.emails.forEach(function(email) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.EMAIL) .setContent(email.address)); }); } if (person.phones) { person.phones.forEach(function(phone) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.PHONE) .setContent(phone.value)); }); } if (person.organizations) { person.organizations.forEach(function(org) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MEMBERSHIP) .setContent(org.department)); }); } if (person.locations) { person.locations.forEach(function(location) { var formattedLocation = Utilities.formatString("%s<br>%s", location.area, location.buildingId); section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MAP_PIN) .setContent(formattedLocation)); }); } return CardService.newCardBuilder() .setHeader(cardHeader) .addSection(section) .build(); } /** * Builds a card for displaying a list of people * * @param {Object[]} people - Array of users from the Directory API * @return {Card} Card to display */ function buildTeamListCard_(people) { var resultsSection = CardService.newCardSection(); people.forEach(function(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var title = person.organizations ? person.organizations[0].title : null; var clickAction = CardService.newAction() .setFunctionName("onShowPersonDetails") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({email: person.primaryEmail}); var personSummaryWidget = CardService.newKeyValue() .setContent(person.name.fullName) .setIconUrl(photoUrl) .setOnClickAction(clickAction); if (person.organizations && person.organizations.length) { personSummaryWidget.setBottomLabel(person.organizations[0].title); } resultsSection.addWidget(personSummaryWidget); }); return CardService.newCardBuilder() .addSection(resultsSection) .build(); } /** * Builds the search interface for looking up people. * * @param {string} opt_error - Optional message to include (typically when * contextual search failed.) * @return {Card} Card to display */ function buildSearchCard_(opt_error) { var banner = CardService.newImage() .setImageUrl('https://storage.googleapis.com/gweb-cloudblog-publish/original_images/Workforce_segmentation_1.png'); var searchField = CardService.newTextInput() .setFieldName("query") .setHint("Name or email address") .setTitle("Search for people"); var onSubmitAction = CardService.newAction() .setFunctionName("onSearch") .setLoadIndicator(CardService.LoadIndicator.SPINNER); var submitButton = CardService.newTextButton() .setText("Search") .setOnClickAction(onSubmitAction); var section = CardService.newCardSection() .addWidget(banner) .addWidget(searchField) .addWidget(submitButton); if (opt_error) { var message = CardService.newTextParagraph() .setText("Note: " + opt_error); section.addWidget(message); } return CardService.newCardBuilder() .addSection(section) .build(); } /** * Extracts email addresses from the selected Gmail message. Grabs all emails * from the to/cc/from headers. * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromMessage_(event) { // Fetch currently selected message var accessToken = event.messageMetadata.accessToken; var messageId = event.messageMetadata.messageId; GmailApp.setCurrentMessageAccessToken(accessToken); var message = GmailApp.getMessageById(messageId); if (!message) { return []; } // Parse/emit any email addresses in the to/cc/from headers var splitEmailsRegexp = /\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/gi; var emails = _.union( message.getTo().match(splitEmailsRegexp), message.getCc().match(splitEmailsRegexp), message.getFrom().match(splitEmailsRegexp) ); // Remove any +suffixes in the user name portion to get the canonical email var normalizeRegexp = /(.*)\+.*@(.*)/; emails = emails.map(function(email) { return email.replace(normalizeRegexp, "$1@$2"); }); return filterAndSortEmails_(emails); } /** * Extracts email addresses from the selected Drive item. Grabs all emails * from the file ACLs (if user has permission to view them.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromDrivePermissions_(event) { // Make sure just 1 file selected. if (event.drive.selectedItems.length != 1) { return []; } var itemId = event.drive.selectedItems[0].id; var emails = []; var item = Drive.Files.get(itemId, {fields: "owners, sharingUser"}); if (item.sharingUser) { emails.push(item.sharingUser.emailAddress); } if (item.owners) { item.owners.forEach(function(owner) { emails.push(owner.emailAddress); }); } try { var permissions = Drive.Permissions.list(itemId, {fields: '*'}); if (permissions) { permissions.permissions.forEach(function(permission) { if (permission.type != 'domain') { emails.push(permission.emailAddress); } }); } } catch (e) { // Ignore inability to fetch permissions, may not have access console.warn(e); } return filterAndSortEmails_(emails) } /** * Extracts email addresses from the selected calendar event (attendees.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromCalendarEvent_(event) { if (!event.calendar || !event.calendar.attendees) { return []; } var emails = event.calendar.attendees.map(function(attendee) { return attendee.email; }); return filterAndSortEmails_(emails); } /** * Filter email addresses to include only those in the same * domain and excluding the current user. * * @param {string[]} emails - Array of email addresses * @return {string[]} */ function filterAndSortEmails_(emails) { if (!emails) { return []; } var userEmail = Session.getActiveUser().getEmail(); var domain = userEmail.slice(userEmail.indexOf('@') + 1); emails = emails.filter(function(email) { return _.endsWith(email, domain) && email != userEmail; }); emails = _.uniq(emails); return emails.sort(); } /** * Look up one or more people from the Directory API. May omit items * if email addresses aren't valid domain users. * * @param {string[]} emails - Array of email addresses to fetch * @return {Object[]} Array of user objects. */ function fetchPeople_(emails) { if (!emails || emails.length == 0) { return []; } return emails.map(fetchPerson_).filter(function(item) { return item != null && item.primaryEmail; }); } /** * Look up a single person from the Directory API. * * @param {string} email - Email addresses to fetch * @return {Object} User object or null if not a valid user */ function fetchPerson_(email) { if (!email) { return null; } // Check cache first var person = CacheService.getUserCache().get(email); if (person && person.primaryEmail) { return JSON.parse(person); } try { person = AdminDirectory.Users.get( email, { projection: 'full', viewType: 'domain_public'}); CacheService.getUserCache().put(email, JSON.stringify(person)); return person; } catch (e) { // Ignore error, may not be valid domain user anymore. console.warn(e); } return null; } /** * Search for people from the Directory API by name or email address. * * @param {string} query - Name or email address to search for. * @return {Object[]} Array of user objects. */ function queryPeople_(query) { try { var options = { query: query, maxResults: 10, customer: 'my_customer', projection: 'full', viewType: 'domain_public' }; var results = AdminDirectory.Users.list(options); var cacheValues = results.users.reduce(function(map, person) { map[person.primaryEmail] = JSON.stringify(person); return map; }, {}); CacheService.getUserCache().putAll(cacheValues); return results.users; } catch (e) { // Ignore error console.warn(e); } return []; }
appsscript.json
{ "timeZone": "America/Denver", "dependencies": { "enabledAdvancedServices": [{ "userSymbol": "Drive", "serviceId": "drive", "version": "v3" }, { "userSymbol": "AdminDirectory", "serviceId": "admin", "version": "directory_v1" }], "libraries": [{ "userSymbol": "LodashGS", "libraryId": "1SQ0PlSMwndIuOAgtVJdjxsuXueECtY9OGejVDS37ckSVbMll73EXf2PW", "version": "5" }] }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.user.readonly", "https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.addons.current.message.metadata", "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/calendar.addons.current.event.read", "https://www.googleapis.com/auth/drive.addons.metadata.readonly", "https://www.googleapis.com/auth/drive.file" ], "urlFetchWhitelist": [], "runtimeVersion": "V8", "addOns": { "common": { "name": "Team List", "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/people_black_24dp.png", "layoutProperties": { "primaryColor": "#4285f4", "secondaryColor": "#ea4335" }, "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "universalActions": [{ "label": "Feedback", "openLink": "https://github.com/googleworkspace/add-ons-samples/issues" }], "openLinkUrlPrefixes": [ "https://github.com/googleworkspace/add-ons-samples/" ] }, "gmail": { "contextualTriggers": [{ "unconditional": { }, "onTriggerFunction": "onGmailMessageSelected" }] }, "drive": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "onItemsSelectedTrigger": { "runFunction": "onDriveItemsSelected" } }, "calendar": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "eventOpenTrigger": { "runFunction": "onCalendarEventOpen" }, "currentEventAccess": "READ" } } }
অবদানকারী
এই নমুনা Google ডেভেলপার বিশেষজ্ঞদের সাহায্যে Google দ্বারা রক্ষণাবেক্ষণ করা হয়।
পরবর্তী পদক্ষেপ
- অ্যাড-অন সহ Google Workspace প্রসারিত করুন
- Google Workspace অ্যাড-অন তৈরি করুন
- একটি অ্যাপ প্রকাশ করুন
কোডিং লেভেল : ইন্টারমিডিয়েট
সময়কাল : 45 মিনিট
প্রকল্পের ধরন : Google Workspace অ্যাড-অন
উদ্দেশ্য
- বুঝুন সমাধান কি করে।
- সমাধানের মধ্যে অ্যাপস স্ক্রিপ্ট পরিষেবাগুলি কী করে তা বুঝুন।
- পরিবেশ স্থাপন করুন।
- স্ক্রিপ্ট সেট আপ করুন।
- স্ক্রিপ্ট চালান।
এই সমাধান সম্পর্কে
আপনি Google Workspace-এ কাজ করার সময় আপনার প্রতিষ্ঠানে যাদের সাথে সহযোগিতা করেন তাদের সম্পর্কে ইমেল, ফোন নম্বর এবং বিভাগের মতো তথ্য দেখান। আপনি Gmail বার্তাগুলির প্রতিক্রিয়া, একটি Google ড্রাইভ ফাইল সম্পাদনা করার সময়, বা Google ক্যালেন্ডার ইভেন্টগুলি দেখার সময় এই তথ্যটি দেখতে পারেন৷
এটা কিভাবে কাজ করে
স্ক্রিপ্টটি সক্রিয় বার্তা, ফাইল বা ইভেন্ট থেকে ইমেল ঠিকানা পায়। প্রেক্ষাপটের উপর নির্ভর করে, এতে Gmail বার্তা প্রাপক, ড্রাইভ ফাইল সম্পাদক এবং ক্যালেন্ডার ইভেন্টে অংশগ্রহণকারীরা অন্তর্ভুক্ত থাকতে পারে। স্ক্রিপ্ট শুধুমাত্র আপনার প্রতিষ্ঠানের ইমেল ঠিকানার জন্য তথ্য দেখায়.
অ্যাপস স্ক্রিপ্ট পরিষেবা
এই সমাধানটি নিম্নলিখিত পরিষেবাগুলি ব্যবহার করে:
- অ্যাডমিন SDK ডিরেক্টরি উন্নত পরিষেবা - ডিরেক্টরি API ব্যবহার করে লোকেদের জন্য অনুসন্ধান করে৷
- বেস পরিষেবা - ইমেল ঠিকানাগুলি ফিল্টার করতে এবং অনুসন্ধান ফলাফলে বর্তমান ব্যবহারকারীকে না দেখানোর জন্য সেশন ক্লাস ব্যবহার করে।
- ক্যাশে পরিষেবা - ডিরেক্টরি API থেকে একক ব্যক্তির সন্ধান করার সময় প্রথমে ক্যাশে অনুসন্ধান করে।
- ক্যালেন্ডার পরিষেবা - যদি প্রসঙ্গটি একটি ক্যালেন্ডার ইভেন্ট হয়, তাহলে সক্রিয় ইভেন্ট থেকে ইমেল ঠিকানা পায়।
- কার্ড পরিষেবা - অ্যাড-অনের ব্যবহারকারী ইন্টারফেস তৈরি করে।
- ড্রাইভ পরিষেবা - যদি প্রসঙ্গটি একটি ড্রাইভ ফাইল হয়, ব্যবহারকারীর সক্রিয় ফাইলে দেখার অনুমতি থাকলে সহযোগীদের ইমেল ঠিকানাগুলি পায়৷
- Gmail পরিষেবা - যদি প্রসঙ্গটি একটি Gmail বার্তা হয়, তাহলে সক্রিয় Gmail বার্তার To, Cc এবং From ক্ষেত্রগুলি থেকে ইমেল ঠিকানাগুলি পায়৷
পূর্বশর্ত
- ইন্টারনেট অ্যাক্সেস সহ একটি ওয়েব ব্রাউজার।
- একটি Google Workspace অ্যাকাউন্ট (আপনার অ্যাডমিনিস্ট্রেটরের অনুমোদনের প্রয়োজন হতে পারে)।
- একটি Google ক্লাউড প্রকল্প ।
আপনার পরিবেশ সেট আপ করুন
Google ক্লাউড কনসোলে আপনার ক্লাউড প্রকল্প খুলুন
যদি এটি ইতিমধ্যে খোলা না থাকে, তাহলে ক্লাউড প্রকল্পটি খুলুন যা আপনি এই নমুনার জন্য ব্যবহার করতে চান:
- Google ক্লাউড কনসোলে, একটি প্রকল্প নির্বাচন করুন পৃষ্ঠাতে যান।
- আপনি যে Google ক্লাউড প্রকল্পটি ব্যবহার করতে চান সেটি নির্বাচন করুন। অথবা, প্রকল্প তৈরি করুন ক্লিক করুন এবং অন-স্ক্রীন নির্দেশাবলী অনুসরণ করুন। আপনি যদি একটি Google ক্লাউড প্রকল্প তৈরি করেন, তাহলে আপনাকে প্রকল্পটির জন্য বিলিং চালু করতে হতে পারে।
অ্যাডমিন SDK API চালু করুন
এই কুইকস্টার্ট অ্যাডমিন SDK API ডিরেক্টরির উন্নত পরিষেবা ব্যবহার করে, যা অ্যাডমিন SDK API অ্যাক্সেস করে।
Google API ব্যবহার করার আগে, আপনাকে একটি Google ক্লাউড প্রকল্পে সেগুলি চালু করতে হবে। আপনি একটি একক Google ক্লাউড প্রকল্পে এক বা একাধিক API চালু করতে পারেন৷আপনার ক্লাউড প্রকল্পে, অ্যাডমিন SDK API চালু করুন।
OAuth সম্মতি স্ক্রীন কনফিগার করুন
Google Workspace অ্যাড-অনগুলির জন্য একটি সম্মতি স্ক্রিন কনফিগারেশন প্রয়োজন। আপনার অ্যাড-অনের OAuth সম্মতি স্ক্রীন কনফিগার করলে তা নির্ধারণ করে যে Google ব্যবহারকারীদের কাছে কী প্রদর্শন করে।
- Google ক্লাউড কনসোলে, মেনু > API এবং পরিষেবা > OAuth সম্মতি স্ক্রীনে যান।
- ব্যবহারকারীর প্রকারের জন্য অভ্যন্তরীণ নির্বাচন করুন, তারপরে তৈরি করুন ক্লিক করুন।
- Complete the app registration form, then click Save and Continue .
আপাতত, আপনি স্কোপ যোগ করা এড়িয়ে যেতে পারেন এবং সেভ এবং কন্টিনিউ এ ক্লিক করতে পারেন। ভবিষ্যতে, যখন আপনি আপনার Google Workspace সংস্থার বাইরে ব্যবহারের জন্য একটি অ্যাপ তৈরি করবেন, তখন আপনাকে অবশ্যই ব্যবহারকারীর ধরন পরিবর্তন করে External তে পরিবর্তন করতে হবে এবং তারপরে, আপনার অ্যাপের প্রয়োজনীয় অনুমোদনের সুযোগ যোগ করতে হবে।
- আপনার অ্যাপ নিবন্ধন সারাংশ পর্যালোচনা করুন. পরিবর্তন করতে, সম্পাদনা ক্লিক করুন। অ্যাপ নিবন্ধন ঠিক আছে বলে মনে হলে, ড্যাশবোর্ডে ফিরে যান ক্লিক করুন।
স্ক্রিপ্ট সেট আপ করুন
অ্যাপস স্ক্রিপ্ট প্রকল্প তৈরি করুন
টিম তালিকা অ্যাপস স্ক্রিপ্ট প্রকল্প খুলতে নিম্নলিখিত বোতামে ক্লিক করুন.
প্রকল্প খুলুনওভারভিউ
ক্লিক করুন।ওভারভিউ পৃষ্ঠায়, একটি অনুলিপি তৈরি করুন ক্লিক করুন .
ক্লাউড প্রকল্প নম্বর কপি করুন
- Google ক্লাউড কনসোলে, মেনু > IAM & Admin > সেটিংসে যান।
- প্রকল্প নম্বর ক্ষেত্রে, মানটি অনুলিপি করুন।
অ্যাপস স্ক্রিপ্ট প্রকল্পের ক্লাউড প্রকল্প সেট করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, প্রকল্প সেটিংস ক্লিক করুন .
- Google ক্লাউড প্ল্যাটফর্ম (GCP) প্রকল্পের অধীনে, প্রকল্প পরিবর্তন করুন ক্লিক করুন।
- GCP প্রকল্প নম্বরে , Google ক্লাউড প্রকল্প নম্বর পেস্ট করুন।
- সেট প্রকল্প ক্লিক করুন.
একটি পরীক্ষা স্থাপনা ইনস্টল করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, সম্পাদক ক্লিক করুন।
-
Code.gs
ফাইলটি খুলুন এবং রান এ ক্লিক করুন। অনুরোধ করা হলে, স্ক্রিপ্ট অনুমোদন করুন. - Deploy > Test deployments এ ক্লিক করুন।
- ইনস্টল > সম্পন্ন ক্লিক করুন।
স্ক্রিপ্ট চালান
- একটি Gmail বার্তা, ক্যালেন্ডার ইভেন্ট বা ড্রাইভ ফাইল খুলুন।
- ডান সাইডবারে, টিম লিস্ট অ্যাড-অন খুলুন।
- অনুরোধ করা হলে, অ্যাড-অন অনুমোদন করুন।
- অ্যাড-অন টিমের সদস্যদের সম্পর্কে তথ্য দেখায় বা নির্দেশ করে যে বার্তা, ইভেন্ট বা ফাইলে কোনও দলের সদস্য নেই।
- দলের সদস্যদের খুঁজে পেতে, লোকেদের জন্য অনুসন্ধানে ক্লিক করুন এবং একটি নাম বা ইমেল ঠিকানা লিখুন৷ অনুসন্ধানে ক্লিক করুন।
কোড পর্যালোচনা করুন
এই সমাধানের জন্য অ্যাপস স্ক্রিপ্ট কোড পর্যালোচনা করতে, নীচের উৎস কোড দেখুন ক্লিক করুন:
সোর্স কোড দেখুন
Code.gs
// Copyright 2022 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Sample Google Workspace Add-on that displays profile information about people // the user is collaborating with. Collaborators are based on the context -- // recipients of a gmail message, Drive file ACLs, or event attendees. // // Profile information is from the Directory API in the Admin SDK. As a result, // the add-on only shows information for email addresses in the same domain // as as the current user. Different strategies can be used for other use cases, // such as integration with a CRM where the focus may be on external email // addresses/customers. // See https://github.com/contributorpw/lodashgs var _ = LodashGS.load(); /** * Renders the home page for the add-on. Used in all host apps when * no context selected. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onHomePage(event) { var card = buildSearchCard_(); return [card]; } /** * Renders the contextual interface for a Gmail message. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onGmailMessageSelected(event) { var emails = extractEmailsFromMessage_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current message."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a calendar event. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onCalendarEventOpen(event) { var emails = extractEmailsFromCalendarEvent_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current event."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a selected Drive file. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onDriveItemsSelected(event) { // For demo, only allow single select on files. if (event.drive.selectedItems.length != 1) { var message = "To view team members collaborating on a file, select one file only."; var card = buildSearchCard_(message); return [card]; } var selectedItem = event.drive.selectedItems[0]; if (!selectedItem.addonHasFileScopePermission) { // Need file access to read ACL, ask user to authorize. var authorizeFilesAction = CardService.newAction() .setFunctionName("onAuthorizeDriveFiles") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({id: selectedItem.id}); var authorizationMessage = CardService.newTextParagraph() .setText("To view the people on your team the file is shared with, click *Authorize* to grant access."); var authorizeButton = CardService.newTextButton() .setText("Authorize") .setOnClickAction(authorizeFilesAction); var card = CardService.newCardBuilder() .addSection(CardService.newCardSection() .addWidget(authorizationMessage) .addWidget(authorizeButton)) .build(); return [card]; } // Have access, extract ACLs to find co-workers var emails = extractEmailsFromDrivePermissions_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current file."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Handles the click for requesting drive file access. * * @param {Object} event - current add-on event * @return {ActionResponse} Request to authorize access to a drive item */ function onAuthorizeDriveFiles(event) { var id = event.parameters.id; return CardService.newDriveItemsSelectedActionResponseBuilder() .requestFileScope(id) .build(); } /** * Handles the user search request. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onSearch(event) { if (!event.formInputs || !event.formInputs.query) { var notification = CardService.newNotification() .setText("Enter a query before searching."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var query = event.formInputs.query[0]; var people = queryPeople_(query); if (!people || people.length == 0) { var notification = CardService.newNotification().setText("No people found."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var card = buildTeamListCard_(people); var navigation = CardService.newNavigation().pushCard(card); return CardService.newActionResponseBuilder() .setNavigation(navigation) .build(); } /** * Handles the drill down to view detailed information about a person. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onShowPersonDetails(event) { var person = fetchPerson_(event.parameters.email); var card = buildPersonDetailsCard_(person); return [card] } /** * Builds a card for displaying detailed information about a team member. Currently only shows * a small subset of available information for demo purposes. * * @param {Object} person - User object from the Directory API * @return {Card} Card to display */ function buildPersonDetailsCard_(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var cardHeader = CardService.newCardHeader() .setImageUrl(photoUrl) .setImageStyle(CardService.ImageStyle.CIRCLE) .setTitle(person.name.fullName) if (person.organizations && person.organizations.length) { cardHeader.setSubtitle(person.organizations[0].title); } var section = CardService.newCardSection(); if (person.emails) { person.emails.forEach(function(email) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.EMAIL) .setContent(email.address)); }); } if (person.phones) { person.phones.forEach(function(phone) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.PHONE) .setContent(phone.value)); }); } if (person.organizations) { person.organizations.forEach(function(org) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MEMBERSHIP) .setContent(org.department)); }); } if (person.locations) { person.locations.forEach(function(location) { var formattedLocation = Utilities.formatString("%s<br>%s", location.area, location.buildingId); section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MAP_PIN) .setContent(formattedLocation)); }); } return CardService.newCardBuilder() .setHeader(cardHeader) .addSection(section) .build(); } /** * Builds a card for displaying a list of people * * @param {Object[]} people - Array of users from the Directory API * @return {Card} Card to display */ function buildTeamListCard_(people) { var resultsSection = CardService.newCardSection(); people.forEach(function(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var title = person.organizations ? person.organizations[0].title : null; var clickAction = CardService.newAction() .setFunctionName("onShowPersonDetails") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({email: person.primaryEmail}); var personSummaryWidget = CardService.newKeyValue() .setContent(person.name.fullName) .setIconUrl(photoUrl) .setOnClickAction(clickAction); if (person.organizations && person.organizations.length) { personSummaryWidget.setBottomLabel(person.organizations[0].title); } resultsSection.addWidget(personSummaryWidget); }); return CardService.newCardBuilder() .addSection(resultsSection) .build(); } /** * Builds the search interface for looking up people. * * @param {string} opt_error - Optional message to include (typically when * contextual search failed.) * @return {Card} Card to display */ function buildSearchCard_(opt_error) { var banner = CardService.newImage() .setImageUrl('https://storage.googleapis.com/gweb-cloudblog-publish/original_images/Workforce_segmentation_1.png'); var searchField = CardService.newTextInput() .setFieldName("query") .setHint("Name or email address") .setTitle("Search for people"); var onSubmitAction = CardService.newAction() .setFunctionName("onSearch") .setLoadIndicator(CardService.LoadIndicator.SPINNER); var submitButton = CardService.newTextButton() .setText("Search") .setOnClickAction(onSubmitAction); var section = CardService.newCardSection() .addWidget(banner) .addWidget(searchField) .addWidget(submitButton); if (opt_error) { var message = CardService.newTextParagraph() .setText("Note: " + opt_error); section.addWidget(message); } return CardService.newCardBuilder() .addSection(section) .build(); } /** * Extracts email addresses from the selected Gmail message. Grabs all emails * from the to/cc/from headers. * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromMessage_(event) { // Fetch currently selected message var accessToken = event.messageMetadata.accessToken; var messageId = event.messageMetadata.messageId; GmailApp.setCurrentMessageAccessToken(accessToken); var message = GmailApp.getMessageById(messageId); if (!message) { return []; } // Parse/emit any email addresses in the to/cc/from headers var splitEmailsRegexp = /\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/gi; var emails = _.union( message.getTo().match(splitEmailsRegexp), message.getCc().match(splitEmailsRegexp), message.getFrom().match(splitEmailsRegexp) ); // Remove any +suffixes in the user name portion to get the canonical email var normalizeRegexp = /(.*)\+.*@(.*)/; emails = emails.map(function(email) { return email.replace(normalizeRegexp, "$1@$2"); }); return filterAndSortEmails_(emails); } /** * Extracts email addresses from the selected Drive item. Grabs all emails * from the file ACLs (if user has permission to view them.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromDrivePermissions_(event) { // Make sure just 1 file selected. if (event.drive.selectedItems.length != 1) { return []; } var itemId = event.drive.selectedItems[0].id; var emails = []; var item = Drive.Files.get(itemId, {fields: "owners, sharingUser"}); if (item.sharingUser) { emails.push(item.sharingUser.emailAddress); } if (item.owners) { item.owners.forEach(function(owner) { emails.push(owner.emailAddress); }); } try { var permissions = Drive.Permissions.list(itemId, {fields: '*'}); if (permissions) { permissions.permissions.forEach(function(permission) { if (permission.type != 'domain') { emails.push(permission.emailAddress); } }); } } catch (e) { // Ignore inability to fetch permissions, may not have access console.warn(e); } return filterAndSortEmails_(emails) } /** * Extracts email addresses from the selected calendar event (attendees.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromCalendarEvent_(event) { if (!event.calendar || !event.calendar.attendees) { return []; } var emails = event.calendar.attendees.map(function(attendee) { return attendee.email; }); return filterAndSortEmails_(emails); } /** * Filter email addresses to include only those in the same * domain and excluding the current user. * * @param {string[]} emails - Array of email addresses * @return {string[]} */ function filterAndSortEmails_(emails) { if (!emails) { return []; } var userEmail = Session.getActiveUser().getEmail(); var domain = userEmail.slice(userEmail.indexOf('@') + 1); emails = emails.filter(function(email) { return _.endsWith(email, domain) && email != userEmail; }); emails = _.uniq(emails); return emails.sort(); } /** * Look up one or more people from the Directory API. May omit items * if email addresses aren't valid domain users. * * @param {string[]} emails - Array of email addresses to fetch * @return {Object[]} Array of user objects. */ function fetchPeople_(emails) { if (!emails || emails.length == 0) { return []; } return emails.map(fetchPerson_).filter(function(item) { return item != null && item.primaryEmail; }); } /** * Look up a single person from the Directory API. * * @param {string} email - Email addresses to fetch * @return {Object} User object or null if not a valid user */ function fetchPerson_(email) { if (!email) { return null; } // Check cache first var person = CacheService.getUserCache().get(email); if (person && person.primaryEmail) { return JSON.parse(person); } try { person = AdminDirectory.Users.get( email, { projection: 'full', viewType: 'domain_public'}); CacheService.getUserCache().put(email, JSON.stringify(person)); return person; } catch (e) { // Ignore error, may not be valid domain user anymore. console.warn(e); } return null; } /** * Search for people from the Directory API by name or email address. * * @param {string} query - Name or email address to search for. * @return {Object[]} Array of user objects. */ function queryPeople_(query) { try { var options = { query: query, maxResults: 10, customer: 'my_customer', projection: 'full', viewType: 'domain_public' }; var results = AdminDirectory.Users.list(options); var cacheValues = results.users.reduce(function(map, person) { map[person.primaryEmail] = JSON.stringify(person); return map; }, {}); CacheService.getUserCache().putAll(cacheValues); return results.users; } catch (e) { // Ignore error console.warn(e); } return []; }
appsscript.json
{ "timeZone": "America/Denver", "dependencies": { "enabledAdvancedServices": [{ "userSymbol": "Drive", "serviceId": "drive", "version": "v3" }, { "userSymbol": "AdminDirectory", "serviceId": "admin", "version": "directory_v1" }], "libraries": [{ "userSymbol": "LodashGS", "libraryId": "1SQ0PlSMwndIuOAgtVJdjxsuXueECtY9OGejVDS37ckSVbMll73EXf2PW", "version": "5" }] }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.user.readonly", "https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.addons.current.message.metadata", "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/calendar.addons.current.event.read", "https://www.googleapis.com/auth/drive.addons.metadata.readonly", "https://www.googleapis.com/auth/drive.file" ], "urlFetchWhitelist": [], "runtimeVersion": "V8", "addOns": { "common": { "name": "Team List", "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/people_black_24dp.png", "layoutProperties": { "primaryColor": "#4285f4", "secondaryColor": "#ea4335" }, "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "universalActions": [{ "label": "Feedback", "openLink": "https://github.com/googleworkspace/add-ons-samples/issues" }], "openLinkUrlPrefixes": [ "https://github.com/googleworkspace/add-ons-samples/" ] }, "gmail": { "contextualTriggers": [{ "unconditional": { }, "onTriggerFunction": "onGmailMessageSelected" }] }, "drive": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "onItemsSelectedTrigger": { "runFunction": "onDriveItemsSelected" } }, "calendar": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "eventOpenTrigger": { "runFunction": "onCalendarEventOpen" }, "currentEventAccess": "READ" } } }
অবদানকারী
এই নমুনা Google ডেভেলপার বিশেষজ্ঞদের সাহায্যে Google দ্বারা রক্ষণাবেক্ষণ করা হয়।
পরবর্তী পদক্ষেপ
- অ্যাড-অন সহ Google Workspace প্রসারিত করুন
- Google Workspace অ্যাড-অন তৈরি করুন
- একটি অ্যাপ প্রকাশ করুন
কোডিং লেভেল : ইন্টারমিডিয়েট
সময়কাল : 45 মিনিট
প্রকল্পের ধরন : Google Workspace অ্যাড-অন
উদ্দেশ্য
- বুঝুন সমাধান কি করে।
- সমাধানের মধ্যে অ্যাপস স্ক্রিপ্ট পরিষেবাগুলি কী করে তা বুঝুন।
- পরিবেশ স্থাপন করুন।
- স্ক্রিপ্ট সেট আপ করুন।
- স্ক্রিপ্ট চালান।
এই সমাধান সম্পর্কে
আপনি Google Workspace-এ কাজ করার সময় আপনার প্রতিষ্ঠানে যাদের সাথে সহযোগিতা করেন তাদের সম্পর্কে ইমেল, ফোন নম্বর এবং বিভাগের মতো তথ্য দেখান। আপনি Gmail বার্তাগুলির প্রতিক্রিয়া, একটি Google ড্রাইভ ফাইল সম্পাদনা করার সময়, বা Google ক্যালেন্ডার ইভেন্টগুলি দেখার সময় এই তথ্যটি দেখতে পারেন৷
এটা কিভাবে কাজ করে
স্ক্রিপ্টটি সক্রিয় বার্তা, ফাইল বা ইভেন্ট থেকে ইমেল ঠিকানা পায়। প্রেক্ষাপটের উপর নির্ভর করে, এতে Gmail বার্তা প্রাপক, ড্রাইভ ফাইল সম্পাদক এবং ক্যালেন্ডার ইভেন্টে অংশগ্রহণকারীরা অন্তর্ভুক্ত থাকতে পারে। স্ক্রিপ্ট শুধুমাত্র আপনার প্রতিষ্ঠানের ইমেল ঠিকানার জন্য তথ্য দেখায়.
অ্যাপস স্ক্রিপ্ট পরিষেবা
এই সমাধানটি নিম্নলিখিত পরিষেবাগুলি ব্যবহার করে:
- অ্যাডমিন SDK ডিরেক্টরি উন্নত পরিষেবা - ডিরেক্টরি API ব্যবহার করে লোকেদের জন্য অনুসন্ধান করে৷
- বেস পরিষেবা - ইমেল ঠিকানাগুলি ফিল্টার করতে এবং অনুসন্ধান ফলাফলে বর্তমান ব্যবহারকারীকে না দেখানোর জন্য সেশন ক্লাস ব্যবহার করে।
- ক্যাশে পরিষেবা - ডিরেক্টরি API থেকে একক ব্যক্তির সন্ধান করার সময় প্রথমে ক্যাশে অনুসন্ধান করে।
- ক্যালেন্ডার পরিষেবা - যদি প্রসঙ্গটি একটি ক্যালেন্ডার ইভেন্ট হয়, তাহলে সক্রিয় ইভেন্ট থেকে ইমেল ঠিকানা পায়।
- কার্ড পরিষেবা - অ্যাড-অনের ব্যবহারকারী ইন্টারফেস তৈরি করে।
- ড্রাইভ পরিষেবা - যদি প্রসঙ্গটি একটি ড্রাইভ ফাইল হয়, ব্যবহারকারীর সক্রিয় ফাইলে দেখার অনুমতি থাকলে সহযোগীদের ইমেল ঠিকানাগুলি পায়৷
- Gmail পরিষেবা - যদি প্রসঙ্গটি একটি Gmail বার্তা হয়, তাহলে সক্রিয় Gmail বার্তার To, Cc এবং From ক্ষেত্রগুলি থেকে ইমেল ঠিকানাগুলি পায়৷
পূর্বশর্ত
- ইন্টারনেট অ্যাক্সেস সহ একটি ওয়েব ব্রাউজার।
- একটি Google Workspace অ্যাকাউন্ট (আপনার অ্যাডমিনিস্ট্রেটরের অনুমোদনের প্রয়োজন হতে পারে)।
- একটি Google ক্লাউড প্রকল্প ।
আপনার পরিবেশ সেট আপ করুন
Google ক্লাউড কনসোলে আপনার ক্লাউড প্রকল্প খুলুন
যদি এটি ইতিমধ্যে খোলা না থাকে, তাহলে ক্লাউড প্রকল্পটি খুলুন যা আপনি এই নমুনার জন্য ব্যবহার করতে চান:
- Google ক্লাউড কনসোলে, একটি প্রকল্প নির্বাচন করুন পৃষ্ঠাতে যান।
- আপনি যে Google ক্লাউড প্রকল্পটি ব্যবহার করতে চান সেটি নির্বাচন করুন। অথবা, প্রকল্প তৈরি করুন ক্লিক করুন এবং অন-স্ক্রীন নির্দেশাবলী অনুসরণ করুন। আপনি যদি একটি Google ক্লাউড প্রকল্প তৈরি করেন, তাহলে আপনাকে প্রকল্পটির জন্য বিলিং চালু করতে হতে পারে।
অ্যাডমিন SDK API চালু করুন
এই কুইকস্টার্ট অ্যাডমিন SDK API ডিরেক্টরির উন্নত পরিষেবা ব্যবহার করে, যা অ্যাডমিন SDK API অ্যাক্সেস করে।
Google API ব্যবহার করার আগে, আপনাকে একটি Google ক্লাউড প্রকল্পে সেগুলি চালু করতে হবে। আপনি একটি একক Google ক্লাউড প্রকল্পে এক বা একাধিক API চালু করতে পারেন৷আপনার ক্লাউড প্রকল্পে, অ্যাডমিন SDK API চালু করুন।
OAuth সম্মতি স্ক্রীন কনফিগার করুন
Google Workspace অ্যাড-অনগুলির জন্য একটি সম্মতি স্ক্রিন কনফিগারেশন প্রয়োজন। আপনার অ্যাড-অনের OAuth সম্মতি স্ক্রীন কনফিগার করলে তা নির্ধারণ করে যে Google ব্যবহারকারীদের কাছে কী প্রদর্শন করে।
- Google ক্লাউড কনসোলে, মেনু > API এবং পরিষেবা > OAuth সম্মতি স্ক্রীনে যান।
- ব্যবহারকারীর প্রকারের জন্য অভ্যন্তরীণ নির্বাচন করুন, তারপরে তৈরি করুন ক্লিক করুন।
- অ্যাপ রেজিস্ট্রেশন ফর্মটি পূরণ করুন, তারপর সংরক্ষণ করুন এবং চালিয়ে যান-এ ক্লিক করুন।
আপাতত, আপনি স্কোপ যোগ করা এড়িয়ে যেতে পারেন এবং সেভ এবং কন্টিনিউ এ ক্লিক করতে পারেন। ভবিষ্যতে, যখন আপনি আপনার Google Workspace সংস্থার বাইরে ব্যবহারের জন্য একটি অ্যাপ তৈরি করবেন, তখন আপনাকে অবশ্যই ব্যবহারকারীর ধরন পরিবর্তন করে External তে পরিবর্তন করতে হবে এবং তারপরে, আপনার অ্যাপের প্রয়োজনীয় অনুমোদনের সুযোগ যোগ করতে হবে।
- আপনার অ্যাপ নিবন্ধন সারাংশ পর্যালোচনা করুন. পরিবর্তন করতে, সম্পাদনা ক্লিক করুন। অ্যাপ নিবন্ধন ঠিক আছে বলে মনে হলে, ড্যাশবোর্ডে ফিরে যান ক্লিক করুন।
স্ক্রিপ্ট সেট আপ করুন
অ্যাপস স্ক্রিপ্ট প্রকল্প তৈরি করুন
টিম তালিকা অ্যাপস স্ক্রিপ্ট প্রকল্প খুলতে নিম্নলিখিত বোতামে ক্লিক করুন.
প্রকল্প খুলুনওভারভিউ
ক্লিক করুন।ওভারভিউ পৃষ্ঠায়, একটি অনুলিপি তৈরি করুন ক্লিক করুন .
ক্লাউড প্রকল্প নম্বর কপি করুন
- Google ক্লাউড কনসোলে, মেনু > IAM & Admin > সেটিংসে যান।
- প্রকল্প নম্বর ক্ষেত্রে, মানটি অনুলিপি করুন।
অ্যাপস স্ক্রিপ্ট প্রকল্পের ক্লাউড প্রকল্প সেট করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, প্রকল্প সেটিংস ক্লিক করুন .
- Google ক্লাউড প্ল্যাটফর্ম (GCP) প্রকল্পের অধীনে, প্রকল্প পরিবর্তন করুন ক্লিক করুন।
- GCP প্রকল্প নম্বরে , Google ক্লাউড প্রকল্প নম্বর পেস্ট করুন।
- সেট প্রকল্প ক্লিক করুন.
একটি পরীক্ষা স্থাপনা ইনস্টল করুন
- আপনার অনুলিপি করা Apps স্ক্রিপ্ট প্রকল্পে, সম্পাদক ক্লিক করুন।
-
Code.gs
ফাইলটি খুলুন এবং রান এ ক্লিক করুন। অনুরোধ করা হলে, স্ক্রিপ্ট অনুমোদন করুন. - Deploy > Test deployments এ ক্লিক করুন।
- ইনস্টল > সম্পন্ন ক্লিক করুন।
স্ক্রিপ্ট চালান
- একটি Gmail বার্তা, ক্যালেন্ডার ইভেন্ট বা ড্রাইভ ফাইল খুলুন।
- ডান সাইডবারে, টিম লিস্ট অ্যাড-অন খুলুন।
- অনুরোধ করা হলে, অ্যাড-অন অনুমোদন করুন।
- অ্যাড-অন টিমের সদস্যদের সম্পর্কে তথ্য দেখায় বা নির্দেশ করে যে বার্তা, ইভেন্ট বা ফাইলে কোনও দলের সদস্য নেই।
- দলের সদস্যদের খুঁজে পেতে, লোকেদের জন্য অনুসন্ধানে ক্লিক করুন এবং একটি নাম বা ইমেল ঠিকানা লিখুন৷ অনুসন্ধানে ক্লিক করুন।
কোড পর্যালোচনা করুন
এই সমাধানের জন্য অ্যাপস স্ক্রিপ্ট কোড পর্যালোচনা করতে, নীচের উৎস কোড দেখুন ক্লিক করুন:
সোর্স কোড দেখুন
Code.gs
// Copyright 2022 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // Sample Google Workspace Add-on that displays profile information about people // the user is collaborating with. Collaborators are based on the context -- // recipients of a gmail message, Drive file ACLs, or event attendees. // // Profile information is from the Directory API in the Admin SDK. As a result, // the add-on only shows information for email addresses in the same domain // as as the current user. Different strategies can be used for other use cases, // such as integration with a CRM where the focus may be on external email // addresses/customers. // See https://github.com/contributorpw/lodashgs var _ = LodashGS.load(); /** * Renders the home page for the add-on. Used in all host apps when * no context selected. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onHomePage(event) { var card = buildSearchCard_(); return [card]; } /** * Renders the contextual interface for a Gmail message. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onGmailMessageSelected(event) { var emails = extractEmailsFromMessage_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current message."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a calendar event. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onCalendarEventOpen(event) { var emails = extractEmailsFromCalendarEvent_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current event."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Renders the contextual interface for a selected Drive file. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onDriveItemsSelected(event) { // For demo, only allow single select on files. if (event.drive.selectedItems.length != 1) { var message = "To view team members collaborating on a file, select one file only."; var card = buildSearchCard_(message); return [card]; } var selectedItem = event.drive.selectedItems[0]; if (!selectedItem.addonHasFileScopePermission) { // Need file access to read ACL, ask user to authorize. var authorizeFilesAction = CardService.newAction() .setFunctionName("onAuthorizeDriveFiles") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({id: selectedItem.id}); var authorizationMessage = CardService.newTextParagraph() .setText("To view the people on your team the file is shared with, click *Authorize* to grant access."); var authorizeButton = CardService.newTextButton() .setText("Authorize") .setOnClickAction(authorizeFilesAction); var card = CardService.newCardBuilder() .addSection(CardService.newCardSection() .addWidget(authorizationMessage) .addWidget(authorizeButton)) .build(); return [card]; } // Have access, extract ACLs to find co-workers var emails = extractEmailsFromDrivePermissions_(event); var people = fetchPeople_(emails); if (people.length == 0) { var card = buildSearchCard_("No team members found for current file."); return [card]; } var card = buildTeamListCard_(people) return [card]; } /** * Handles the click for requesting drive file access. * * @param {Object} event - current add-on event * @return {ActionResponse} Request to authorize access to a drive item */ function onAuthorizeDriveFiles(event) { var id = event.parameters.id; return CardService.newDriveItemsSelectedActionResponseBuilder() .requestFileScope(id) .build(); } /** * Handles the user search request. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onSearch(event) { if (!event.formInputs || !event.formInputs.query) { var notification = CardService.newNotification() .setText("Enter a query before searching."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var query = event.formInputs.query[0]; var people = queryPeople_(query); if (!people || people.length == 0) { var notification = CardService.newNotification().setText("No people found."); return CardService.newActionResponseBuilder() .setNotification(notification) .build(); } var card = buildTeamListCard_(people); var navigation = CardService.newNavigation().pushCard(card); return CardService.newActionResponseBuilder() .setNavigation(navigation) .build(); } /** * Handles the drill down to view detailed information about a person. * * @param {Object} event - current add-on event * @return {Card[]} Card(s) to display */ function onShowPersonDetails(event) { var person = fetchPerson_(event.parameters.email); var card = buildPersonDetailsCard_(person); return [card] } /** * Builds a card for displaying detailed information about a team member. Currently only shows * a small subset of available information for demo purposes. * * @param {Object} person - User object from the Directory API * @return {Card} Card to display */ function buildPersonDetailsCard_(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var cardHeader = CardService.newCardHeader() .setImageUrl(photoUrl) .setImageStyle(CardService.ImageStyle.CIRCLE) .setTitle(person.name.fullName) if (person.organizations && person.organizations.length) { cardHeader.setSubtitle(person.organizations[0].title); } var section = CardService.newCardSection(); if (person.emails) { person.emails.forEach(function(email) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.EMAIL) .setContent(email.address)); }); } if (person.phones) { person.phones.forEach(function(phone) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.PHONE) .setContent(phone.value)); }); } if (person.organizations) { person.organizations.forEach(function(org) { section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MEMBERSHIP) .setContent(org.department)); }); } if (person.locations) { person.locations.forEach(function(location) { var formattedLocation = Utilities.formatString("%s<br>%s", location.area, location.buildingId); section.addWidget(CardService.newKeyValue() .setIcon(CardService.Icon.MAP_PIN) .setContent(formattedLocation)); }); } return CardService.newCardBuilder() .setHeader(cardHeader) .addSection(section) .build(); } /** * Builds a card for displaying a list of people * * @param {Object[]} people - Array of users from the Directory API * @return {Card} Card to display */ function buildTeamListCard_(people) { var resultsSection = CardService.newCardSection(); people.forEach(function(person) { var photoUrl = person.thumbnailPhotoUrl ? person.thumbnailPhotoUrl : "https://ssl.gstatic.com/s2/profiles/images/silhouette200.png"; var title = person.organizations ? person.organizations[0].title : null; var clickAction = CardService.newAction() .setFunctionName("onShowPersonDetails") .setLoadIndicator(CardService.LoadIndicator.SPINNER) .setParameters({email: person.primaryEmail}); var personSummaryWidget = CardService.newKeyValue() .setContent(person.name.fullName) .setIconUrl(photoUrl) .setOnClickAction(clickAction); if (person.organizations && person.organizations.length) { personSummaryWidget.setBottomLabel(person.organizations[0].title); } resultsSection.addWidget(personSummaryWidget); }); return CardService.newCardBuilder() .addSection(resultsSection) .build(); } /** * Builds the search interface for looking up people. * * @param {string} opt_error - Optional message to include (typically when * contextual search failed.) * @return {Card} Card to display */ function buildSearchCard_(opt_error) { var banner = CardService.newImage() .setImageUrl('https://storage.googleapis.com/gweb-cloudblog-publish/original_images/Workforce_segmentation_1.png'); var searchField = CardService.newTextInput() .setFieldName("query") .setHint("Name or email address") .setTitle("Search for people"); var onSubmitAction = CardService.newAction() .setFunctionName("onSearch") .setLoadIndicator(CardService.LoadIndicator.SPINNER); var submitButton = CardService.newTextButton() .setText("Search") .setOnClickAction(onSubmitAction); var section = CardService.newCardSection() .addWidget(banner) .addWidget(searchField) .addWidget(submitButton); if (opt_error) { var message = CardService.newTextParagraph() .setText("Note: " + opt_error); section.addWidget(message); } return CardService.newCardBuilder() .addSection(section) .build(); } /** * Extracts email addresses from the selected Gmail message. Grabs all emails * from the to/cc/from headers. * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromMessage_(event) { // Fetch currently selected message var accessToken = event.messageMetadata.accessToken; var messageId = event.messageMetadata.messageId; GmailApp.setCurrentMessageAccessToken(accessToken); var message = GmailApp.getMessageById(messageId); if (!message) { return []; } // Parse/emit any email addresses in the to/cc/from headers var splitEmailsRegexp = /\b[A-Z0-9._%+-]+@(?:[A-Z0-9-]+\.)+[A-Z]{2,6}\b/gi; var emails = _.union( message.getTo().match(splitEmailsRegexp), message.getCc().match(splitEmailsRegexp), message.getFrom().match(splitEmailsRegexp) ); // Remove any +suffixes in the user name portion to get the canonical email var normalizeRegexp = /(.*)\+.*@(.*)/; emails = emails.map(function(email) { return email.replace(normalizeRegexp, "$1@$2"); }); return filterAndSortEmails_(emails); } /** * Extracts email addresses from the selected Drive item. Grabs all emails * from the file ACLs (if user has permission to view them.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromDrivePermissions_(event) { // Make sure just 1 file selected. if (event.drive.selectedItems.length != 1) { return []; } var itemId = event.drive.selectedItems[0].id; var emails = []; var item = Drive.Files.get(itemId, {fields: "owners, sharingUser"}); if (item.sharingUser) { emails.push(item.sharingUser.emailAddress); } if (item.owners) { item.owners.forEach(function(owner) { emails.push(owner.emailAddress); }); } try { var permissions = Drive.Permissions.list(itemId, {fields: '*'}); if (permissions) { permissions.permissions.forEach(function(permission) { if (permission.type != 'domain') { emails.push(permission.emailAddress); } }); } } catch (e) { // Ignore inability to fetch permissions, may not have access console.warn(e); } return filterAndSortEmails_(emails) } /** * Extracts email addresses from the selected calendar event (attendees.) * * @param {Object} event - current add-on event * @return {string[]} Array of email addresses. */ function extractEmailsFromCalendarEvent_(event) { if (!event.calendar || !event.calendar.attendees) { return []; } var emails = event.calendar.attendees.map(function(attendee) { return attendee.email; }); return filterAndSortEmails_(emails); } /** * Filter email addresses to include only those in the same * domain and excluding the current user. * * @param {string[]} emails - Array of email addresses * @return {string[]} */ function filterAndSortEmails_(emails) { if (!emails) { return []; } var userEmail = Session.getActiveUser().getEmail(); var domain = userEmail.slice(userEmail.indexOf('@') + 1); emails = emails.filter(function(email) { return _.endsWith(email, domain) && email != userEmail; }); emails = _.uniq(emails); return emails.sort(); } /** * Look up one or more people from the Directory API. May omit items * if email addresses aren't valid domain users. * * @param {string[]} emails - Array of email addresses to fetch * @return {Object[]} Array of user objects. */ function fetchPeople_(emails) { if (!emails || emails.length == 0) { return []; } return emails.map(fetchPerson_).filter(function(item) { return item != null && item.primaryEmail; }); } /** * Look up a single person from the Directory API. * * @param {string} email - Email addresses to fetch * @return {Object} User object or null if not a valid user */ function fetchPerson_(email) { if (!email) { return null; } // Check cache first var person = CacheService.getUserCache().get(email); if (person && person.primaryEmail) { return JSON.parse(person); } try { person = AdminDirectory.Users.get( email, { projection: 'full', viewType: 'domain_public'}); CacheService.getUserCache().put(email, JSON.stringify(person)); return person; } catch (e) { // Ignore error, may not be valid domain user anymore. console.warn(e); } return null; } /** * Search for people from the Directory API by name or email address. * * @param {string} query - Name or email address to search for. * @return {Object[]} Array of user objects. */ function queryPeople_(query) { try { var options = { query: query, maxResults: 10, customer: 'my_customer', projection: 'full', viewType: 'domain_public' }; var results = AdminDirectory.Users.list(options); var cacheValues = results.users.reduce(function(map, person) { map[person.primaryEmail] = JSON.stringify(person); return map; }, {}); CacheService.getUserCache().putAll(cacheValues); return results.users; } catch (e) { // Ignore error console.warn(e); } return []; }
appsscript.json
{ "timeZone": "America/Denver", "dependencies": { "enabledAdvancedServices": [{ "userSymbol": "Drive", "serviceId": "drive", "version": "v3" }, { "userSymbol": "AdminDirectory", "serviceId": "admin", "version": "directory_v1" }], "libraries": [{ "userSymbol": "LodashGS", "libraryId": "1SQ0PlSMwndIuOAgtVJdjxsuXueECtY9OGejVDS37ckSVbMll73EXf2PW", "version": "5" }] }, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/admin.directory.user.readonly", "https://www.googleapis.com/auth/gmail.addons.execute", "https://www.googleapis.com/auth/gmail.addons.current.message.metadata", "https://www.googleapis.com/auth/calendar.addons.execute", "https://www.googleapis.com/auth/calendar.addons.current.event.read", "https://www.googleapis.com/auth/drive.addons.metadata.readonly", "https://www.googleapis.com/auth/drive.file" ], "urlFetchWhitelist": [], "runtimeVersion": "V8", "addOns": { "common": { "name": "Team List", "logoUrl": "https://www.gstatic.com/images/icons/material/system/1x/people_black_24dp.png", "layoutProperties": { "primaryColor": "#4285f4", "secondaryColor": "#ea4335" }, "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "universalActions": [{ "label": "Feedback", "openLink": "https://github.com/googleworkspace/add-ons-samples/issues" }], "openLinkUrlPrefixes": [ "https://github.com/googleworkspace/add-ons-samples/" ] }, "gmail": { "contextualTriggers": [{ "unconditional": { }, "onTriggerFunction": "onGmailMessageSelected" }] }, "drive": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "onItemsSelectedTrigger": { "runFunction": "onDriveItemsSelected" } }, "calendar": { "homepageTrigger": { "runFunction": "onHomePage", "enabled": true }, "eventOpenTrigger": { "runFunction": "onCalendarEventOpen" }, "currentEventAccess": "READ" } } }
অবদানকারী
এই নমুনা Google ডেভেলপার বিশেষজ্ঞদের সাহায্যে Google দ্বারা রক্ষণাবেক্ষণ করা হয়।
পরবর্তী পদক্ষেপ
- অ্যাড-অন সহ Google Workspace প্রসারিত করুন
- Google Workspace অ্যাড-অন তৈরি করুন
- একটি অ্যাপ প্রকাশ করুন