如何使用 Google Apps 脚本创建一个自动日历,并在其之上使用开源

获取这个小脚本,它可以完成 Zapier 所做的一切,甚至更多。
313 位读者喜欢此文。
Poll: Upcoming open source conferences

Dafne Cholet。 CC BY-SA 2.0。

 

在会议上发言可能很难。本教程旨在帮助您更好地管理提交提案征集 (CFP) 和在会议上发言的“行政管理”工作。

我主要讲授开源工具或开源组织概念,我认为与他人分享非常重要。如果您有兴趣发言,请查看本文中提到的一些会议,阅读这个很棒的资源,如果您需要任何提示或帮助,请随时联系我。阅读一些针对组织者的优秀技巧也可能会有所帮助,因为它们可能会帮助您更好地制定可以被接受的提案。

除了作为演讲者,我还担任 DevOpsDays KC 的 CFP 评审委员会成员,所以我对 CFP 流程的主题策划方面略知一二。温馨提示:我们发现去年有人用我们来做 A/B 测试他们的标题,我们的评分受到了标题的影响。描述是完全相同的。

管理会议提交

我将向科技行业内外的许多会议提交提案。这在精神上需要维护很多东西。我已经花了几个星期来创建提案并使其恰到好处,但我将如何管理我所有的提交?如果每个人都使用papercall.io就好了,因为我可以在那里跟踪它们。但是,我们都知道永远不会有一个统一的标准。有些会议有一个仪表板来跟踪你的提交,有些会议发送电子邮件,还有些会议什么都不做。最后一种是最难追踪的。

所以,我决定从一个简单的 Google 表单自动生成的表格开始。我添加了我认为提交后不同阶段需要的信息类型。我添加了事件名称、地点、开始和结束日期、CFP 结束日期、事件和 CFP URL、演讲者通知日期以及我提交的演讲列表的条目。我将在完成每个 CFP 时填写此信息。

本来到此为止就结束了,但我意识到如果没有日历之类的东西,全年跟踪所有这些信息将会非常困难。我首先想到的是用 JavaScript 从头开始创建一个,但这只是我内心的开发者在作祟。在我压制了他之后,我决定可能有一种更简单的方法。

谷歌搜索开始了。Zapier 在其教程中具有非常好的 SEO,所以我决定使用它将我的 Google 表格连接到我的 Google 日历。我创建了几个特殊的日历——一个用于跟踪我尚未被接受的会议,另一个用于跟踪那些已经接受我的提案的会议。我还创建了一个单独的表格,以便我可以添加额外的列,例如接受状态以及接受了哪些演讲。

一旦我掌握了诀窍,与 Zapier 的集成就非常容易了。我配置了一个复杂的 Zap,它为流程的每个阶段创建了不同类型的日历事件,包括 CFP 结束日期的单独事件。它运行得很好,然后我意识到我使用的是试用版,两周后它将全部消失。不好!这不是我的工作,所以我不想为我可以构建(和撰写)的东西付费。

所以,谷歌搜索再次开始。我发现了 Google Apps 脚本 (GAS) 以及一些 Google 表格和日历 API 库。当我开始使用某些库时,我意识到仅为了使身份验证正常工作就需要付出相当大的努力,然后我必须在某个地方运行它。

即使 GAS 不是开源的,我还是决定使用它,并使我在其之上所做的一切都完全开放和免费。我首先导航到我的表格,然后单击工具,然后单击脚本编辑器。这打开了 一个 IDE,我可以在那里开始。

Google Apps Scripts IDE

经过相当多的试验和错误,我最终得到了一个小型脚本,它可以完成 Zapier 所做的一切,甚至更多。对于我有限的需求来说,它也是完全免费的。它每五分钟运行一次,并且是完全幂等的。我也不必运行该脚本——Google 会在某个地方执行此操作,而我不知道其他任何事情。

构建我的日历自动化

在我们开始之前,此自动化有一个注意事项。我对 Google 表格不是很擅长,所以我无法弄清楚如何在 Responses 表格更新时更新 Results 表格。这意味着我必须进入并将当前单元格中的规则复制到需要填写的任何新单元格中。

另外,我推荐 这个 Chrome 扩展程序,它允许你使用 GitLab 存储库来存储你的脚本。我在开发结束时了解了它,然后立即使用它将我完成的代码放入版本控制的存储库中。你现在可以从 我的存储库 分叉,将我的脚本从你控制的存储库中导入到你的 GAS 编辑器中。

现在我将逐步介绍代码,以便让你更好地了解正在发生的事情以及原因,然后再展示最终产品。

function createTrigger() {
  ScriptApp.newTrigger('updateEvents')
      .timeBased()
      .everyMinutes(5)
      .create();
}

第一个函数创建一个触发器,该触发器调用 updateEvents 方法。此脚本设置为每五分钟运行一次,这很好,因为它具有幂等性。小心不要以一种删除幂等性的方式编辑脚本,这意味着你需要删除数百个事件。好消息是,你可以快速删除日历上的所有事件,并恢复到幂等脚本,以便在几分钟内恢复到完全正常运行状态。你也可以反转 deleteEvent 函数中的检查,并且每次脚本运行时,该脚本将为每个事件删除一个条目。这帮助我慢慢地纠正了开发过程中的错误。

// Deletes an event if it exists
function deleteEvent(event) {
  if (typeof event != 'undefined') {
    Logger.log("Deleting event %s", event.getTitle())
    event.deleteEvent()
  }
}

让我们跳过下一个函数,然后转到 第三个函数deleteEvent。此函数将 CalendarEvent 作为参数,检查它是否存在,如果存在则删除它。这只是一个小型的辅助脚本,它清理了我的代码。

// Get Sheet and both Calendars.
  var sheet = SpreadsheetApp.openByUrl("https://xxxxxx").getSheetByName("Results")
  var submittedCalendar = CalendarApp.getCalendarById("xxxxxxx")
  var acceptedCalendar = CalendarApp.getCalendarById("xxxxxx")

现在,让我们解决 大函数updateEvents。这将执行所有逻辑。首先,我需要建立到每个日历和我正在使用的表格的身份验证。这非常快速和容易,并且在脚本的第一次执行中处理。你可以通过导航到你的表格并从地址栏中获取 URL 来获取表格 URL。你可以按照 这些说明 获取日历 ID。我建议不要使用名称用于任何一个,因为你可能稍后想要更改它并忘记更新引用。

  // Get data from Sheet
  var data = sheet.getDataRange().getValues()
  
  var events = []
  
  // This loop parses the data from the spreadsheet and adds it to an array
  for (var i = 1; i < data.length; i++) {
    // Skips blank rows
    if (data[i][0] == "") {
      break
    }
    
    // Gets the speaker notification date if one exists or sets it to the start date of the conference
    var speakerNotificationDate = new Date(data[i][4].getTime() + 2.88e7)
    if (data[i][5] != '') {
      speakerNotificationDate = new Date(data[i][5].getTime() + 2.88e7)
    }
    
    // Uses the first row, headers, as keys with the values being assigned to each. Then the object is pushed onto the array.
    var event = {}
    event[data[0][0]] = data[i][0]
    event[data[0][1]] = data[i][1]
    event[data[0][2]] = data[i][2]
    event[data[0][3]] = data[i][3]
    event[data[0][4]] = new Date(data[i][4].getTime() + 2.88e7) // Update the time to 8 a.m.
    event[data[0][5]] = speakerNotificationDate
    event[data[0][6]] = new Date(data[i][6].getTime() + 2.88e7) // Update the time to 8 a.m.
    event[data[0][7]] = new Date(data[i][7].getTime() + 7.2e7) // Update the time to 8 p.m.
    event[data[0][8]] = data[i][8]
    event[data[0][9]] = data[i][9]
    event[data[0][10]] = data[i][10]
    events.push(event)
  }

身份验证完成后,我继续从表格中获取所有数据。脚本 检查以确保存在事件名称;如果没有,它将不会继续处理该行,而将转到下一个条目。每一行都 解析为一个对象,其中是列标题,是被处理的行中单元格的值。我对日期和空数据进行了一些清理,以确保脚本的其余部分正常运行。

我遇到了一些事件在日历上的显示问题,我发现最好将 开始时间设置为早上 8 点,并将 结束时间设置为晚上 8 点。避免 0000 小时似乎是在解决我遇到的许多问题的理想选择。脚本还确保 输入了演讲者通知日期,或者它 将日期设置为事件的开始日期。

if (events[i]["Accepted"] == "Yes") {
      if (typeof acceptedCalendar.getEvents(events[i]["Start date"], events[i]["End date"], {search: events[i]["Event Name"] + " - " + events[i]["Talks Accepted"]})[0] == 'undefined') {
        acceptedCalendar.createEvent(events[i]["Event Name"] + " - " + events[i]["Talks Accepted"], events[i]["Start date"], events[i]["End date"], {location: events[i]["Location"], description: "Event URL: " + events[i]["Event URL"] + "\n\nCFP URL: " + events[i]["CFP URL"] + "\n\nTalks Accepted: " + events[i]["Talks Accepted"]}).setColor(CalendarApp.EventColor.GREEN).addEmailReminder(40320).addPopupReminder(40320)
        Logger.log("Created accepted event on accepted calendar")
      }
      
      if (typeof submittedCalendar.getEvents(events[i]["Start date"], events[i]["End date"], {search: events[i]["Event Name"] + " - Accepted"})[0] == 'undefined') {
        submittedCalendar.createEvent(events[i]["Event Name"] + " - Accepted", events[i]["Start date"], events[i]["End date"], {location: events[i]["Location"], description: "Event URL: " + events[i]["Event URL"] + "\n\nCFP URL: " + events[i]["CFP URL"] + "\n\nTalks Accepted: " + events[i]["Talks Accepted"]}).setColor(CalendarApp.EventColor.GREEN)
        Logger.log("Created accepted event on submitted calendar")
      }
      
      deleteEvent(submittedCalendar.getEvents(events[i]["Start date"], events[i]["End date"], {search: events[i]["Event Name"] + " - Submitted"})[0])
      deleteEvent(submittedCalendar.getEvents(events[i]["Start date"], events[i]["End date"], {search: events[i]["Event Name"] + " - Rejected"})[0])
    }

然后脚本 循环遍历每个事件,以将其添加到相应的日历。你可以在上面看到添加和删除事件的示例。表上有一个名为 Accepted 的属性,如果会议已接受我的任何提交,则该属性将填充单词 Yes如果为真,那么 将创建两个日历事件,每个日历上各一个。

由于我有两个单独的日历,因此我可以轻松查看我正在发言的所有会议。对于 已接受的日历,我首先 在日历上搜索该事件,并返回 Google 日历响应的数组中的第一项。我假设如果存在事件,那么它就是我想要的事件。我使用非常具体的搜索,其中包括接受的演讲,因此它应该只匹配该事件。如果没有事件,那么我 创建一个新事件,其中包含所有相关信息,确保颜色为绿色(因为这是接受的日历上事件的颜色)。我还设置了提前四周的通知。这有助于我确保我的演示文稿已准备就绪并且我的所有行程都已预订。

以完全相同的方式将事件添加到 已提交日历 中。然而,由于它位于一个包含许多其他类似事件的日历上,因此会在会议标题中添加“ - Accepted”。然后,我删除标题中包含" - Submitted"" - Rejected"关键字的任何事件,因为它们不再需要了。

如果 Accepted 属性设置为 No,我会在已提交日历上创建一个拒绝的事件。我还需删除其他三个可能的日历条目。然后,如果在Accepted列中存在任何其他值或没有值,我可以假设它已提交但尚未接受或拒绝。所以,我创建了一个" - Submitted"事件,并删除任何可能的拒绝或接受的事件

  if (typeof submittedCalendar.getEvents(events[i]["CFP end date"], events[i]["Start date"], {search: events[i]["Event Name"] + " - CFP"})[0] == 'undefined') {
      submittedCalendar.createAllDayEvent(events[i]["Event Name"] + " - CFP", events[i]["CFP end date"], {location: events[i]["Location"], description: "CFP End Date: " + events[i]["CFP end date"] + "\n\nSpeaker Notification Date: " + events[i]["Speaker notification date"] + "\n\nEvent URL: " + events[i]["Event URL"] + "\n\nCFP URL: " + events[i]["CFP URL"] + "\n\nTalks Submitted: " + events[i]["Talks Submitted"]}).setColor(CalendarApp.EventColor.YELLOW).addEmailReminder(10080).addPopupReminder(10080)
      Logger.log("Created CFP event")
    }

最后,我确保每个CFP都有一个条目。这创建在CFP的已发布截止日期,颜色更改为黄色,并在截止日期前七天设置通知。虽然我可能已经提交了会议,但有时会发生一些事情,我想更改提交或我决定提交另一个提案。此外,我可以添加一个事件,而不提交任何演讲(尚未),这样我会提醒自己在CFP关闭之前提交。目前,向事件添加另一个演讲不会更新该事件,除非我手动在我的日历中删除该事件。这种情况非常罕见,所以我不太担心。

Google calendar of speaking engagements

把所有这些放在一起不应该花费很长时间,我认为它真的会帮助我更好地管理我的日程安排。这是一个我的四月日历的预览,目前只有一个已接受的会议,还有几个会议正在等待CFP关闭。此外,您可以看到Submitted有两种颜色(这是首先使用Zapier的产物)。这表明搜索工作正常,并且仅匹配标题。如果我想花时间,我可以更改它,但这不会困扰我,也不应该影响你。

如果您想为此项目贡献任何增强功能,请随时在GitLab上提交合并请求

撰写成功的提案

在过去的几年里,我一直在会议上发表演讲。第一年,我只是扔出了一堆随机的提案,这些提案是为每个活动定制的,并尽量避免重复。这意味着我几乎没有申请任何活动,而且基本上都被拒绝了。我想那一年我只在一个会议上发了言。

我决定那不是最佳选择。我的提案都不太好,因为我不能在任何一个提案上花费太多时间。我也意识到这很像销售,我基本上会被拒绝。因此,我决定创建三个提案,稍微为每个CFP定制它们,并在提案被接受后定制实际的演示文稿。我全年都在不断修改我的演示文稿,根据每个会议的重点和时间限制进行修改,所以我基本上从不演示相同的东西两次。

我想那一年我在10个会议上发了言,包括All Day DevOps、Jenkins World、All Things Open和LISA(仅举几个较大的)。还有许多较小的会议,组织者和社区都很好,我推荐它们,例如GlueCon、WeRISE Atlanta、BSidesKC和RevolutionConf。您可以通过我的网站查看幻灯片或观看视频

我使用三个提案的策略让我可以向更多的会议提交申请,找到最适合我和会议组织者的提案。它效果很好,我可能向50个会议提交了申请。跟踪所有这些是不可能的。我一直担心我会错过一封关于被接受的邮件,或者接受两个同时发生的会议。幸运的是,我认为这两件事都没有发生。

2018年,我决定减少旅行,所以我只在几个会议上发了言,而且它们都是定制的提交,因为我没有提交很多。我的接受率接近50%。

进入2019年,我计划进行更多的演示,因此我需要一个策略。我再次决定采用一小组演讲。由于我在NAIC工作期间获得的许多新知识,以及帮助该组织进行大规模技术和文化转型,今年将有七个提案。我也希望以一种包括整个产品管道的方式来谈论风险,以及如何利用它来资助转型,这很方便,因为我的新角色是RSA Archer的首席架构师。


接下来阅读

接下来要阅读的内容
Dan Barker
网站:http://danbarker.codes 电子邮件:dan@danbarker.codes

评论已关闭。

Creative Commons License本作品采用知识共享署名-相同方式共享4.0国际许可协议进行许可。
© . All rights reserved.