3
$\begingroup$

I want to extend the functionality of a service available via ServiceConnect, particularly Google Calendar to insert a new event.

That action requieres authentication via OAuth2, and that can be done by

googlecalendar = ServiceConnect["GoogleCalendar"]

The Google API documentation is available here

https://developers.google.com/calendar/v3/reference/events/insert

It indicates that to create an event one needs to POST a body like this

{
  "end": {
    "date": "2018-06-19"
  },
  "start": {
    "date": "2018-06-18"
  }
}

I don't know how to use the already authenticated service connection to POST my request. I'm guessing that OAuthClient`rawoauthdata should help.

I know this code does not work, but I would like to do something like

Module[
{
id=googlecalendar["ID"],
calendarId= "calendarIdString",
url,
body="{
  \"end\": {
    \"date\": \"2018-06-19\"
  },
  \"start\": {
    \"date\": \"2018-06-18\"
  }
}"
},
url=StringTemplate[
 "https://www.googleapis.com/calendar/v3/calendars/`1`/events/insert"
][calendarId];
OAuthClient`rawoauthdata[
 id,
 url,
 { 
  "method" -> "POST",
  "body" -> body
 }
 ]
]

EDIT

As per suggestion by @b3m2a1, I'm open to the possibility of editing the paclet itself, even if that seems to me implies a much more serious task of making the new function consistent with the rest.

In any of the two cases I'm missing the intermediate information that links the high level and still cryptic OAuthClient with the low level construction of a URLFetch call, defining proper headers, body and so on. I don't understand how they relate, and all the examples I can see in other programming languages are high level.

So, I'm restating my question, inside or outside the paclet,

How to implement a function that uses POST and OAuth to achieve inserting an event in Google Calendar?

$\endgroup$
  • 1
    $\begingroup$ I suggest editing the paclet directly. It's really quite easy to do. See, this for instance (and just edit the existing one instead of making a new one). Or duplicate the existing one and make a "GCal" connection or something which you can customize at will. $\endgroup$ – b3m2a1 Jun 19 '18 at 0:41
  • $\begingroup$ @b3m2a1 Thanks for the suggestion. I'm afraid that "really quite easy to do" it's in the eye of the experienced beholder. I haven't been able to understand it, yet. I updated my question to explicitly say that my vacuum in knowledge is the link between a low level headers and body http call, and for me impenetrable OAuthClient functions, for which I can't find much understandable information. $\endgroup$ – rhermans Jun 19 '18 at 8:10
  • 1
    $\begingroup$ Of course. It's only approaching quite easy if you spent a full day or so deconstructing these paclets. I'll post a template answer to get you started. $\endgroup$ – b3m2a1 Jun 19 '18 at 8:20
4
$\begingroup$

Here's an elaboration on my suggestion. More information on why I'm doing what I'm doing is here: Creating a custom web API ServiceConnection

First open the paclet:

SystemOpen @ PacletFind["*_GoogleCalendar"][[1]]["Location"]

Go into the GoogleCalendar.m file and make the following changes:

Add "RawPosts"->{"RawCalendarInsert"} and "Posts"->{"CalendarInsert"} to the lists of supported requests (under googlecalendardata)

Then add a raw data request template that looks like:

googlecalendardata["RawCalendarInsert"] =
 {
  "URL" -> (
    TemplateApply[
      StringForm[
       "https://www.googleapis.com/calendar/v3/calendars/`1`/events", 
       URLEncode[#]]
      ] &),
  "PathParameters" -> {"calendarID"},
  "RequiredParameters" -> {"calendarID", "start", "end"},
  "HTTPSMethod" -> "POST",
  "BodyData" -> {"start", "end"},
  "ResultsFunction" -> googlecalendarimport
  }

And finally add a cooked data function that basically just calls this:

googlecalendarcookeddata["CalendarInsert", id_, args_] :=

 Block[
  {rawdata, invalidParameters, cId, fieldnames, result},
  If[KeyExistsQ[args, "CalendarID"],
   cId = Lookup[args, "CalendarID"];
   If[! MatchQ[cId, _String],
    Message[ServiceExecute::nval, "CalendarID", "GoogleCalendar"];
    Throw[$Failed]
    ],
   Message[ServiceExecute::nparam, "CalendarID"];
   Throw[$Failed]
   ];
  rawdata =
   OAuthClient`rawoauthdata[id, 
    "RawCalendarInsert", {"calendarID" -> ToString[cId]}];
  result = googlecalendarimport[rawdata]
  ]

You can pretty up that function however you so choose.

You may need to do a reset of the data for this to get the changes to merge. To do that run:

Quiet[
 Once[OAuthClient`Private`oauthservicesdata["GoogleCalendar"]] =.;
 ServiceDisconnect["GoogleCalendar"];
 ServiceConnections`Private`findandloadServicePaclet["GoogleCalendar"];
 ]

Make sure to preserve any changes you make somewhere as if WRI updates its paclet you'll need to reapply them (but it looks like they haven't in over a year).

You can also simply duplicate the entire paclet and change only the name in the PacletInfo.m file (to say GoogleCalendar2) or something. Then run RebuildPacletData and you can use that edited paclet.

$\endgroup$
  • $\begingroup$ Thanks! That's what I have (pastebin.com/raw/qqXGCMfU) and I'm unsure how to call it. It complains "ServiceExecute::nparam: The parameter start is required" , when it's present. googlecalendar["CalendarInsert", "CalendarID" -> "xxxIDxxx", "start" -> "2018-06-21", "end" -> "2018-06-23"] $\endgroup$ – rhermans Jun 21 '18 at 7:52
  • 1
    $\begingroup$ @rhermans Ah shit. Forgot to include the args part. When it calls the rawoauthdata you’ll need to include the args other than the calendar ID. $\endgroup$ – b3m2a1 Jun 21 '18 at 7:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.