英文:
Google Contacts (People API) query not working in script called from trigger script
问题
UPDATE
我已经确定这是一个身份验证问题。由于某种原因,当我从编辑器运行该函数时,我可以执行People API查询,但是从触发器运行时,我从执行日志中收到以下消息:
> GoogleJsonResponseException: 调用people.people.searchContacts的API调用失败,错误为:请求缺少必需的身份验证凭据。预期的OAuth 2访问令牌、登录Cookie或其他有效的身份验证凭据。请参见https://developers.google.com/identity/sign-in/web/devconsole-project。
> at searchContactsMain(searchContactsMain:6:29)
> at onEdit(onEdit:21:7)
有人能指导我正确的方向吗?
ORIGINAL POST
首先,我有一个脚本,它获取工作表A1单元格中的文本并使用它查询我的Google联系人。它将所有结果列在B列中。这个脚本是有效的。
function searchContactsTest() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("MAIN");
var query = sheet.getRange('A1').getValue();
let found = People.People.searchContacts({
"query": query,
"readMask": "names,addresses"
});
if (found.results) {
var contactNames = [];
for (i = 0; i < found.results.length; i++) {
let thisResult = found.results[i];
contactNames[i] = [];
contactNames[i].push(thisResult.person.names[0].displayName);
Logger.log(`Result ${i}: ${thisResult.person.names[0].displayName}`);
if(thisResult.person.addresses){
Logger.log(`Result ${i}: ${thisResult.person.addresses[0].streetAddress}`);
}
}
} else {
Logger.log(`No results for ${query}`);
}
if (found.results.length > 0) {
// Write results to column B starting from row 2
sheet.getRange("B2:B").clearContent(); // Clear previous results
var outputRange = sheet.getRange(2, 2, contactNames.length, contactNames[0].length);
outputRange.setValues(contactNames);
}
}
接下来,我想在另一个上下文中使用People.searchContacts查询:当用户点击包含姓名的单元格时。我使用onSelectionChange触发器进行此操作。作为触发器的测试,我将点击的单元格背景颜色设为红色,然后调用我的函数displayContact(range)。
function onSelectionChange(e) {
const range = e.range;
sheetID = range.getSheet().getSheetId().toString();
if (sheetID == "1060200918") //Main front end sheet
{
if (range.getNumRows() === 1 &&
range.getNumColumns() === 1 &&
range.getCell(1, 1).getValue() != '')
{
range.setBackground('red');
displayContact(e);
}
}
}
这也有效。接下来,在displayContact中,我执行People.searchContacts,使用触发事件的单元格值。脚本应该将返回的联系人地址写入A2。
function displayContact(e) {
range = e.range; //**** Getting range from event object ****
sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MAIN");
testOut = sheet.getRange("A2");
//range = sheet.getRange("B2"); //**** Hardcoding the range ****
var query = range.getValue();
//Test to see if we're accessing the cell that was clicked on.
testOut.setValue(query);
let found = People.People.searchContacts({
"query": query,
"readMask": "names,addresses"
//"readMask": "names,addresses,emailAddresses,phoneNumbers"
});
range.setBackground('blue');
if (found.results)
{
let thisResult = found.results[0];
let contactAddress = thisResult.person.addresses[0].streetAddress;
//sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MAIN");
outRange = sheet.getRange("A3");
outRange.clearContent(); // Clear previous results
outRange.setValue(contactAddress);
}
else
{
Logger.log(`No results for ${query}`);
}
}
第11行将我点击的单元格的值(我使用B2)写入A2单元格,以测试事件对象是否被正确传递。这个操作有效。然而,第19行,将单元格背景颜色设为蓝色的测试(Logger对触发的事件不生成任何输出)从不执行。查询从不发生,人的地址从不写入A3。
然而,如果我注释掉第2行,取消注释第7行,并从编辑器中运行它而不是让触发处理程序运行它,一切都完美运行。第7行硬编码范围与我点击的精确相同的单元格。相同的值写入A2。一切都完全相同,唯一的区别是我手动设置范围而不是从事件对象获取,并从编辑器运行脚本。
我做错了什么?
英文:
UPDATE
I've determined that this is an authentication problem. For some reason I can perform People API queries when I'm running the function from the editor, but from the trigger I get this message from the executions log:
> GoogleJsonResponseException: API call to people.people.searchContacts
> failed with error: Request is missing required authentication
> credential. Expected OAuth 2 access token, login cookie or other valid
> authentication credential. See
> https://developers.google.com/identity/sign-in/web/devconsole-project.
> at searchContactsMain(searchContactsMain:6:29)
> at onEdit(onEdit:21:7)
Anyone can point me to the right direction?
ORIGINAL POST
First, I have a script that takes the text in cell A1 of a sheet and queries my Google Contacts using it. It lists all the results to column B. This script works.
function searchContactsTest() {
var sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName("MAIN");
var query = sheet.getRange('A1').getValue();
let found = People.People.searchContacts({
"query": query,
"readMask": "names,addresses"
});
if (found.results) {
var contactNames = [];
for (i = 0; i < found.results.length; i++) {
let thisResult = found.results[i];
contactNames[i] = [];
contactNames[i].push(thisResult.person.names[0].displayName);
Logger.log(`Result ${i}: ${thisResult.person.names[0].displayName}`);
if(thisResult.person.addresses){
Logger.log(`Result ${i}: ${thisResult.person.addresses[0].streetAddress}`);
}
}
} else {
Logger.log(`No results for ${query}`);
}
if (found.results.length > 0) {
// Write results to column B starting from row 2
sheet.getRange("B2:B").clearContent(); // Clear previous results
var outputRange = sheet.getRange(2, 2, contactNames.length, contactNames[0].length);
outputRange.setValues(contactNames);
}
}
Now then, I want to use the People.searchContacts query in another context: when the user clicks on a cell containing a name. I'm using the onSelectionChange trigger for this. I'm turning the clicked cell background red as a test of the trigger, then I call my function displayContact(range).
function onSelectionChange(e) {
const range = e.range;
sheetID = range.getSheet().getSheetId().toString();
if (sheetID == "1060200918") //Main front end sheet
{
if (range.getNumRows() === 1 &&
range.getNumColumns() === 1 &&
range.getCell(1, 1).getValue() != '')
{
range.setBackground('red');
displayContact(e);
}
}
}
This works too.
Next, in displayContact, I do a People.searchContacts, using the value of the cell that triggered the event. The script should write the address of the contact returned to A2.
function displayContact(e) {
range = e.range; //**** Getting range from event object ****
sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MAIN");
testOut = sheet.getRange("A2");
//range = sheet.getRange("B2"); //**** Hardcoding the range ****
var query = range.getValue();
//Test to see if we're accessing the cell that was clicked on.
testOut.setValue(query);
let found = People.People.searchContacts({
"query": query,
"readMask": "names,addresses"
//"readMask": "names,addresses,emailAddresses,phoneNumbers"
});
range.setBackground('blue');
if (found.results)
{
let thisResult = found.results[0];
let contactAddress = thisResult.person.addresses[0].streetAddress;
//sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("MAIN");
outRange = sheet.getRange("A3");
outRange.clearContent(); // Clear previous results
outRange.setValue(contactAddress);
}
else
{
Logger.log(`No results for ${query}`);
}
}
Line 11 writes the value of the cell I'm clicking on (I'm using B2) to cell A2, to test whether the event object is being passed correctly. That works. However, line 19 which turns the cell background blue as a test (Logger produces no output for triggered events) never executes. The query never happens, and the person's address never gets written to A3.
HOWEVER, if I comment out line 2, uncomment line 7, and run it from the editor instead of letting the trigger handler run it, everything works perfectly. Line 7 hardcodes the range to the exact same cell I'm clicking on. The same value gets written to A2. Everything is exactly the same except that I'm setting the range manually rather than getting it from the event object, and running the script from the editor.
What am I doing wrong?
答案1
得分: 2
错误消息与简单触发器相关。在Google文档中这里,它在简单触发器的限制下说明了以下内容:
他们无法访问需要授权的服务。例如,简单触发器无法发送电子邮件,因为Gmail服务需要授权,但简单触发器可以使用匿名的Language服务来翻译短语。
这就是为什么在编辑器下工作,但无法在触发器中进行身份验证的原因。
我建议在这种情况下可能使用可安装触发器,而不是简单触发器。可以通过手动添加触发器(链接)或通过编程方式添加触发器(链接)来实现。但是,请考虑onSelectionChange(e)
不可用的情况。
也许您可以使用onEdit
触发器,并限制更改范围。例如,在单元格A1
中添加一个下拉列表,其中包含所有联系人的名称。一旦用户更改值,它将在B列中粘贴结果,并返回A2
中的地址或代码需要执行的任何内容。
添加一个if
语句,只有在更改仅在A1
中完成的条件下才会执行这些更改。您可以使用getA1Notation()
方法(链接)进行条件判断。
英文:
The error message is related to the simple trigger. In the Google Documentation here, it states the following under the restriction of Simple triggers:
> They cannot access services that require authorization. For example, a
> simple trigger cannot send an email because the Gmail service requires
> authorization, but a simple trigger can translate a phrase with the
> Language service, which is anonymous.
That is why, is working under the editor, but is unable to authenticate using the trigger.
I will recommend to maybe use an installable trigger for this situation, instead of a simple trigger. Either by adding the trigger manually or adding it programmatically. However, take in consideration that the onSelectionChange(e)
is not available.
Maybe you can use an onEdit
trigger, and limit the change to a specific range. For example, adding a drop-down in cell A1
with all the names of the contacts. Once the user change the value, it will paste the results in column B, and return the Address in A2
or whatever the codes need to do.
Add an If
statement that it will only do those changes under the condition that the change is only done in A1
. You can use the method getA1Notation() for the if
statement.
答案2
得分: -3
用这段代码,我与特定人员分享了Google表格通讯录,添加了一个名为"schools"的群组,并将它们添加到该群组并分享。
Google允许最多进行200次查询,所以我获取了整个通讯录和表格,并遇到了 `if(!veri3.includes(veri2[i]))`。
检查通讯录中是否有名为"schools"的群组,如果没有,则添加一个名为"schools"的群组。
英文:
With this code, I share the Google Spreadsheet Phonebook with certain people, add a group called schools to the directory and add them to that group and share them.
Google allows to make a maximum of 200 queries, so I get the whole phonebook and table in Array list and encounter if(!veri3.includes(veri2[i]))
check if there is a group named schools in the phone book, if not, add a group named schools
var groupName = "Okullar";
var groups = People.ContactGroups.list()["contactGroups"];
var group = groups.find(group => group["name"] === groupName);
// 3. CREATE GROUP IF DOESN'T EXIST:
if (!group) {
var groupResource = {
contactGroup: {
name: groupName
}
}
group = People.ContactGroups.create(groupResource);
}
var groupResourceName = group["resourceName"];
get data of sheet named list from table
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Liste");
var dataRange = sheet.getRange(2, 1, sheet.getLastRow()-1, 7)
var data = dataRange.getValues();
Getting the sheet named List in the table into Array list
//Tabloyu Listeye alma
var veri1=[];
for (var i=0;i<data.length;i++){
veri1.push([data[i][3]]);
}
veri3=veri1.flat()
var veri1=[];
for (var i=0;i<data.length;i++){
veri1.push([data[i][3]]);
}
veri3=veri1.flat()
import data from phonebook to Array List
//Rehberi Listeye ALma
var options = {
pageSize:1000,
personFields: 'names,emailAddresses,phoneNumbers,metadata'
};
var callback = People.People.Connections.list
do{
response = callback('people/me',options)
//response.connections.forEach(connection => connection.names ? isim.push(Utilities.formatDate(new Date(connection.metadata.sources[0].updateTime),"GMT", "dd.MM.yyyy")) : '');
response.connections.forEach(connection => connection.emailAddresses ? veri.push(connection.emailAddresses?.map(email => email.value)):'');
options.pageToken = response.nextPageToken
} while(options.pageToken)
veri2=veri.flat()
People Api asks for tokens for subsequent records after a certain registration, that is, there are 2500 contacts in your phone book, it does not show you 2500 records at once, it shows 1000 records so that you can get the next 1000
records options.pageToken = response.nextPageToken
} while(options.pageToken)
this part must be in your code
this section compares two array lists
for (var i=0;i<veri3.length;i++){
if(!veri2.includes(veri3[i]))
Adding a contact that is in the table but not in the phonebook to the phonebook
for (var i=0;i<veri3.length;i++){
if(!veri2.includes(veri3[i]))
{
var contactResource = {
"names": [{
"givenName": data[i][0],
"familyName": data[i][1],
}],
"phoneNumbers": [{
'value': "0"+data[i][4]
}],
"emailAddresses": [{
'value': data[i][3]
}],
"organizations": [{
"name": data[i][6],
"title": data[i][5],
"type": "work"
}],
}
add the contact you added to the phone book to the group called schools
var contactResourceName = People.People.createContact(contactResource)["resourceName"];
var groupResourceName = group["resourceName"];
// 4. ADD CONTACT TO GROUP:
var membersResource = {
"resourceNamesToAdd": [
contactResourceName
]
}
People.ContactGroups.Members.modify(membersResource, groupResourceName);
this code is to delete the person deleted from the table from the phone book
function tablodansilinenlerirehberdensilme()
the whole code
function sharephonebook() {
var veri=[];
var veri2=[];
var veri3=[];
var response;
// grup yapma........................................................
var groupName = "Okullar";
var groups = People.ContactGroups.list()["contactGroups"];
var group = groups.find(group => group["name"] === groupName);
// 3. CREATE GROUP IF DOESN'T EXIST:
if (!group) {
var groupResource = {
contactGroup: {
name: groupName
}
}
group = People.ContactGroups.create(groupResource);
}
var groupResourceName = group["resourceName"];
// tabloyu alma
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Liste");
var dataRange = sheet.getRange(2, 1, sheet.getLastRow()-1, 7)
var data = dataRange.getValues();
//Tabloyu Listeye alma
var veri1=[];
for (var i=0;i<data.length;i++){
veri1.push([data[i][3]]);
}
veri3=veri1.flat()
//Rehberi Listeye ALma
var options = {
pageSize:1000,
personFields: 'names,emailAddresses,phoneNumbers,metadata'
};
var callback = People.People.Connections.list
do{
response = callback('people/me',options)
//response.connections.forEach(connection => connection.names ? isim.push(Utilities.formatDate(new Date(connection.metadata.sources[0].updateTime),"GMT", "dd.MM.yyyy")) : '');
response.connections.forEach(connection => connection.emailAddresses ? veri.push(connection.emailAddresses?.map(email => email.value)):'');
options.pageToken = response.nextPageToken
} while(options.pageToken)
veri2=veri.flat()
for (var i=0;i<veri3.length;i++){
if(!veri2.includes(veri3[i]))
{
var contactResource = {
"names": [{
"givenName": data[i][0],
"familyName": data[i][1],
}],
"phoneNumbers": [{
'value': "0"+data[i][4]
}],
"emailAddresses": [{
'value': data[i][3]
}],
"organizations": [{
"name": data[i][6],
"title": data[i][5],
"type": "work"
}],
}
var contactResourceName = People.People.createContact(contactResource)["resourceName"];
var groupResourceName = group["resourceName"];
// 4. ADD CONTACT TO GROUP:
var membersResource = {
"resourceNamesToAdd": [
contactResourceName
]
}
People.ContactGroups.Members.modify(membersResource, groupResourceName);
Logger.log("bulunmadu"+ veri3[i])
}
}
tablodansilinenlerirehberdensilme()
};
function tablodansilinenlerirehberdensilme() {
//Sabitler
var veri=[];
var veri2=[];
var veri4=[];
let x=1;
//OKullar Grubunun Kodunu Alma
const people = People.ContactGroups.list();
const group1 = people['contactGroups'].find((group) => group['name'] === 'Okullar');
//console.log(group1.resourceName);
// tabloyu alma
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Liste");
var dataRange = sheet.getRange(2, 1, sheet.getLastRow()-1, 7)
var data = dataRange.getValues();
//Tabloyu Listeye alma
var veri1=[];
for (var i=0;i<data.length;i++){
veri1.push([data[i][3]]);
}
veri3=veri1.flat()
//Logger.log(veri3)
//Okullar Rehberini Listeye Alma
var options = {
pageSize:1000,
personFields: 'names,emailAddresses,phoneNumbers,metadata,memberships'
};
var callback = People.People.Connections.list
do{
response = callback('people/me',options)
response.connections.forEach((person) => {
// if contacts/connections is available, print the name of person.
if (person.memberships[0].contactGroupMembership.contactGroupResourceName === group1.resourceName) {
veri.push(person.emailAddresses[0].value);
veri4.push(person.resourceName);
return;
}
});
options.pageToken = response.nextPageToken
} while(options.pageToken)
veri2=veri.flat()
// Logger.log(veri2)
//Tabloyu Listeye Alma
//veri2 flatlanmış Rehberlistesi
for (var i=0;i<veri2.length;i++){
if(!veri3.includes(veri2[i])) {
People.People.deleteContact(veri4[i])
}
}
}
`
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论