我使用了 https://developers.google.com/apps-script/samples/automations/vacation-calendar 上的“填充团队假期日历”示例来构建一个 Apps 脚本来提取所有外出事件进入共享日历。该脚本几个月来一直运行良好。在不对脚本进行任何更改的情况下,现在在调用
Calendar.Events.import(event, TEAM_CALENDAR_ID);
时出现错误:
GoogleJsonResponseException:对 calendar.events.import 的 API 调用失败并出现错误:错误请求
我已经在调试器中运行了脚本,并且该错误并没有提供任何有关错误实际是什么的信息,除了 400 错误请求之外。还有很多与日期/时间格式相关的其他问答,但我导入的
event
变量来自之前对 Calendar.Events.list(...)
的调用,因此它已经是 api 本身生成的 JS 事件对象。
这是一个最小的可重现示例:
let TEAM_CALENDAR_ID = '<calendar id goes here>';
function testImport() {
const now = new Date();
let user = Session.getActiveUser();
// Fetch next 10 events
let events = Calendar.Events.list(user.getEmail(), {
timeMin: now.toISOString(),
singleEvents: true,
orderBy: 'startTime',
maxResults: 10
});
if (events.items.length === 0) {
console.error('No events found');
return;
}
// Use next upcoming event for this user
let event = events.items[0];
// Set event fields
event.organizer = { id: TEAM_CALENDAR_ID };
event.attendees = [];
try {
Calendar.Events.import(event, TEAM_CALENDAR_ID);
} catch (e) {
console.error('Error attempting to import event: %s.', e.toString());
}
}
如何调试这个?
这是整个脚本:
// Set the ID of the team calendar to add events to. You can find the calendar's
// ID on the settings page.
let TEAM_CALENDAR_ID = '<calendar ID here>';
// Set the email address of the Google Group that contains everyone in the team.
// Ensure the group has less than 500 members to avoid timeouts.
let GROUP_EMAIL = '<group email address here>';
let MONTHS_IN_ADVANCE = 3;
/**
* Sets up the script to run automatically every hour.
*/
function setup() {
let triggers = ScriptApp.getProjectTriggers();
if (triggers.length > 0) {
throw new Error('Triggers are already setup.');
}
ScriptApp.newTrigger('sync').timeBased().everyHours(1).create();
// Runs the first sync immediately.
sync();
}
/**
* Looks through the group members' public calendars and adds any
* 'vacation' or 'out of office' events to the team calendar.
*/
function sync() {
// Defines the calendar event date range to search.
let today = new Date();
let maxDate = new Date();
maxDate.setMonth(maxDate.getMonth() + MONTHS_IN_ADVANCE);
// Determines the time the the script was last run.
let lastRun = PropertiesService.getScriptProperties().getProperty('lastRun');
lastRun = lastRun ? new Date(lastRun) : null;
// Gets the list of users in the Google Group.
let users = GroupsApp.getGroupByEmail(GROUP_EMAIL).getUsers();
// For each user, finds Out Of Office events, and import
// each to the team calendar.
let count = 0;
users.forEach(function(user) {
let events = findEvents(user, today, maxDate, lastRun);
events.forEach(function(event) {
importEvent(user, event);
count++;
});
});
PropertiesService.getScriptProperties().setProperty('lastRun', today);
console.log('Updated ' + count + ' events');
}
/**
* In a given user's calendar, looks for Out Of Office events within the
* specified date range and returns any such events found.
* @param {Session.User} user The user to retrieve events for.
* @param {Date} start The starting date of the range to examine.
* @param {Date} end The ending date of the range to examine.
* @param {Date} optSince A date indicating the last time this script was run.
* @return {Calendar.Event[]} An array of calendar events.
*/
function findEvents(user, start, end, optSince) {
let params = {
timeMin: formatDateAsRFC3339(start),
timeMax: formatDateAsRFC3339(end),
showDeleted: true,
};
if (optSince) {
// This prevents the script from examining events that have not been
// modified since the specified date (that is, the last time the
// script was run).
params.updatedMin = formatDateAsRFC3339(optSince);
}
let pageToken = null;
let events = [];
do {
params.pageToken = pageToken;
let response;
try {
response = Calendar.Events.list(user.getEmail(), params);
} catch (e) {
console.error('Error retriving events for %s: %s; skipping',
user, e.toString());
continue;
}
events = events.concat(response.items.filter(function(item) {
return shoudImportEvent(user, item);
}));
pageToken = response.nextPageToken;
} while (pageToken);
return events;
}
/**
* Determines if the given event should be imported into the shared team
* calendar.
* @param {Session.User} user The user that is attending the event.
* @param {Calendar.Event} event The event being considered.
* @return {boolean} True if the event should be imported.
*/
function shoudImportEvent(user, event) {
// Skip events that are not Out Of Office
if (event.eventType != "outOfOffice") {
return false;
}
// If the user is the creator of the event, always imports it.
if (!event.organizer || event.organizer.email == user.getEmail()) {
return true;
}
// Only imports events the user has accepted.
if (!event.attendees) {
return false;
}
let matching = event.attendees.filter(function(attendee) {
return attendee.self;
});
return matching.length > 0 && matching[0].responseStatus == 'accepted';
}
/**
* Imports the given event from the user's calendar into the shared team
* calendar.
* @param {string} username The team member that is attending the event.
* @param {Calendar.Event} event The event to import.
*/
function importEvent(user, event) {
let username = user.getEmail().split('@')[0];
username = username.charAt(0).toUpperCase() + username.slice(1);
event.summary = '[' + username + '] ' + event.summary;
event.organizer = {
id: TEAM_CALENDAR_ID,
};
event.attendees = [];
let action = event.status == "confirmed" ? "Importing" : "Removing";
console.log('%s: %s on %s', action, event.summary, event.start.getDateTime());
try {
Calendar.Events.import(event, TEAM_CALENDAR_ID);
} catch (e) {
console.error('Error attempting to import event: %s. Skipping.',
e.toString());
}
}
/**
* Returns an RFC3339 formated date String corresponding to the given
* Date object.
* @param {Date} date a Date.
* @return {string} a formatted date string.
*/
function formatDateAsRFC3339(date) {
return Utilities.formatDate(date, 'UTC', 'yyyy-MM-dd\'T\'HH:mm:ssZ');
}