Skip to content

Instantly share code, notes, and snippets.

@jmb
Last active December 8, 2022 08:41
Show Gist options
  • Save jmb/33ae3a33d6e8dbffd102 to your computer and use it in GitHub Desktop.
Save jmb/33ae3a33d6e8dbffd102 to your computer and use it in GitHub Desktop.
Google Calendar API v3 widget for Dashing

Description

Dashing widget to display the next and some subsequent Google Calendar events using the Google Calendar API v3.

I use this widget to display my shift calendar - see the screenshot below

Set up

This widget works with API v3 and requires a service account to be set up via the Google Developer's Console. Once a project is set up, enable the Calendar API. On the Credentials page create a new OpenID and download the p12 key file - set up the path to this file in the job file and grant the email address access to the relevant calendar.

The job file defines how many events to get from the calendar and when to start the search. My version gets the next 6 events.

Add to Gemfile:

gem 'google-api-client', '>= 0.8'

Run

bundle install

Download the Moment javascript library and add to your javascript assets. Add #= require moment.js to the application.coffee script. You can also use moment.min.js if you've downloaded that.

class Dashing.GoogleCalendar extends Dashing.Widget
onData: (data) =>
event = rest = null
getEvents = (first, others...) ->
event = first
rest = others
getEvents data.events.items...
start = moment(event.start.dateTime)
end = moment(event.end.dateTime)
@set('event',event)
@set('event_date', start.format('dddd Do MMMM'))
@set('event_times', start.format('HH:mm') + " - " + end.format('HH:mm'))
next_events = []
for next_event in rest
start = moment(next_event.start.dateTime)
start_date = start.format('ddd Do MMM')
start_time = start.format('HH:mm')
next_events.push { summary: next_event.summary, start_date: start_date, start_time: start_time }
@set('next_events', next_events)
<h1 class="subtitle" >Next event:</h1>
<h3 class="times" data-bind="event_date"></h3>
<h2 class="title" data-bind="event.summary"></h2>
<h3 class="times" data-bind="event_times"></h3>
<h4 data-bind="next_count"></h4>
<table class="next">
<tr data-foreach-e='next_events'>
<td data-bind="e.start_date"></td>
<td data-bind="e.start_time"></td>
<td data-bind="e.summary"></td>
</tr>
</table>
<div class="updated-at" data-bind="updatedAtMessage"></div>
# encoding: UTF-8
require 'google/api_client'
require 'date'
require 'time'
require 'digest/md5'
require 'active_support'
require 'active_support/all'
require 'json'
# Update these to match your own apps credentials
service_account_email = '...@developer.gserviceaccount.com' # Email of service account
key_file = '/Path/to/keyfile.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
calendarID = '...@group.calendar.google.com' # Calendar ID.
# Get the Google API client
client = Google::APIClient.new(:application_name => 'Dashing Calendar Widget',
:application_version => '0.0.1')
# Load your credentials for the service account
if not key_file.nil? and File.exists? key_file
key = Google::APIClient::KeyUtils.load_from_pkcs12(key_file, key_secret)
else
key = OpenSSL::PKey::RSA.new ENV['GOOGLE_SERVICE_PK'], key_secret
end
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => 'https://www.googleapis.com/auth/calendar.readonly',
:issuer => service_account_email,
:signing_key => key)
# Start the scheduler
SCHEDULER.every '15m', :first_in => 4 do |job|
# Request a token for our service account
client.authorization.fetch_access_token!
# Get the calendar API
service = client.discovered_api('calendar','v3')
# Start and end dates
now = DateTime.now
result = client.execute(:api_method => service.events.list,
:parameters => {'calendarId' => calendarID,
'timeMin' => now.rfc3339,
'orderBy' => 'startTime',
'singleEvents' => 'true',
'maxResults' => 6}) # How many calendar items to get
send_event('google_calendar', { events: result.data })
end
// ----------------------------------------------------------------------------
// Sass declarations
// ----------------------------------------------------------------------------
$background-color: #47bbb3; //#ec663c;
$title-color: rgba(255, 255, 255, 1);
$subtitle-color: rgba(255, 255, 255, 0.7);
$moreinfo-color: rgba(255, 255, 255, 0.7);
// ----------------------------------------------------------------------------
// Widget-calendar styles
// ----------------------------------------------------------------------------
.widget-google-calendar {
background-color: $background-color;
.subtitle {
color: $subtitle-color;
font-size: 0.75em;
margin: 15px 0;
}
.title {
color: $title-color;
font-size: 1.4em;
}
.times {
font-size: 0.9em;
}
.next {
font-size: 0.75em;
margin-top: 30px;
.tr {
border-bottom: 1px solid $subtitle-color;
.td {
text-align: left;
}
}
}
.more-info {
color: $moreinfo-color;
}
.updated-at {
color: $subtitle-color;
bottom: 5px;
right: 5px;
left: auto;
font-size: 0.5em;
}
&.large h3 {
font-size: 65px;
}
}
@pryonic
Copy link

pryonic commented Jun 5, 2016

This looks exactly what I need - unfortunately I can't get it working. I've followed the steps I believe correctly, and the widget displays but does not show any events.

There's also no errors in the logs - which makes me think I've made mistake on the Google API side of things. Has anything changed here recently? I've set up the job as:

# Update these to match your own apps credentials
service_account_email = 'blahblah-service-account@home-control-panel.iam.gserviceaccount.com' # Email of service account
key_file = '/home/pi/hcp.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
calendarID = 'blah.blah@google.com' # Calendar ID.

I've enabled the calendar API I believe - but do I need to somehow grant the app access to the calendar blah.blah@google.com? One thing I am seeing is that when I refresh the dashboard the "error count" for "Client Errors" on the Google Calendar API page is going up.

Any idea where I can go from here?

@pryonic
Copy link

pryonic commented Jun 5, 2016

Bit more info - I took a copy of the google_calendar.rb file and instead of trying to pass the data retrieved from the Google API i just printed it. I'm not a Ruby programmer at all, but hopefully this is right:

print events: result.data

This is giving me:

{:events=>#<Google::APIClient::Schema::Calendar::V3::Events:0x8ccc4c DATA:{"error"=>{"errors"=>[{"domain"=>"global", "reason"=>"notFound", "message"=>"Not Found"}], "code"=>404, "message"=>"Not Found"}}>}
Something's 404ing, which matches the errors I'm seeing on the Google API screen.

The P12 file and key file are related to the service account yes? Because it's not possible to download P12 files for OAuth 2.0 accounts, only the service accounts

@boolsee
Copy link

boolsee commented Aug 19, 2016

@pryonic I met the same problem, but I solved it finally. I tested the API parameters one-by-one by using "Google API Explorer for Calendar" .
I hope you solved already.

@boolsee
Copy link

boolsee commented Aug 19, 2016

I modified to view single event first and all day event later which is based on @peckeltw code. Because I noticed that the start and end date of all day event is not the same, I changed the end date of all day event to the previous date like this.

event.end.date = (Date.parse(event.end.date)-1).to_s

Now, the end date of all day event will be like this:

ex1) all day event for 1 day (shown on 1 day)
   As-is:  2016-08-19 ~ 2016-08-20  
   To-be:  2016-08-19 ~ 2016-08-19

ex2) all day event for 2 day and more (shown on 2 days)
  As-is:  2016-08-19 ~ 2016-08-21  
  To-be:  2016-08-19 ~ 2016-08-20

Full code is below:

# Temp Array for sort
tmp1 = Array.new # for all day events
tmp2 = Array.new # for single event

 # All day event ?
result.data.items.each do |event|

    all_day_event = 0

    # Append date_time for the all-day event
    if event.start.date_time == nil
        event.start.date_time = event.start.date + " 9:00:00 +0900"
        all_day_event = 1
    end

    if event.end.date_time == nil

        event.end.date = (Date.parse(event.end.date)-1).to_s
        event.end.date_time = event.end.date + " 18:00:00 +0900"
        all_day_event = 1
    end

    # Save the all day events to temp array
    if all_day_event == 1
        tmp1 << event
    else
        tmp2 << event
    end
end

@lanceDamage
Copy link

Newbie here...I'm having trouble understanding the following instructions. Can someone help explain these to me?

"Download the Moment javascript library and add to your javascript assets. Add #= require moment.js to the application.coffee script."

I understand how to download Moment, but I'm not sure where I am suppose to "add to your javascript assets." I am also not sure what is being referred to with "application.coffee script." Is that referring to adding a line of code in google_calendar.coffee? If not, can someone provide the path to this script? Thanks!

@wmreynolds
Copy link

getting a "LoadError: cannot load such file -- google/api_client"

@emilyboda
Copy link

emilyboda commented Mar 19, 2018

@lanceDamage you need to download this thing called moments and add it to /yourproject/assets/javascripts.
cd /yourproject/assets/javascripts
sudo wget https://momentjs.com/downloads/moment.js

Then in the same directory
sudo nano application.coffee
and then add "#= require moment.js" to the top under the other similar line.

@rnhall82
Copy link

@wmreynolds I got that same error. After looking around, I found it was the version of the google api client. From what I read, apparently they made some major changes in it. I was able to fix it by changing that section in the gemfile to read as so:

gem 'google-api-client', '< 0.9'

After I did that, i stopped getting that error message and everything started working for me.

@kny2tl
Copy link

kny2tl commented Jan 21, 2021

I'm struggling to get this running. Let me summary what's current setup (after changing ie Gemfile to gem 'google-api-client', '< 0.9')
Gemfile:

source 'https://rubygems.org'
gem 'smashing'
#gem 'icalendar'
#gem 'google-api-client', '~> 0.53.0'
#gem 'google-api-client', '>= 0.8'
gem 'google-api-client', '< 0.9'

application.coffee:

dashing.js is located in the dashing framework
It includes jquery & batman for you.
#= require dashing.js
#= require_directory .
#= require_tree ../../widgets
#= require moment.js
and so on

ls assets/javascripts/
application.coffee d3-3.2.8.js dashing.gridster.coffee gridster jquery.knob.js moment.js moment-with-locales.js rickshaw-1.4.3.min.js

google_calendar.rb:

Update these to match your own apps credentials
service_account_email = '@.iam.gserviceaccount.com' # Email of service account
key_file = '/home/pi/my-project/keyfile.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
calendarID = '@google.com' # Calendar ID.

AFAIK, set up is fine.
API is allowed. Key was generated. Calendar is shared with that service account.

What I am getting is 'Next event:' but that's it. Enabling debugging I was rewarded with following

{ 753569630 rufus-scheduler intercepted an error:
  753569630   job:
  753569630     Rufus::Scheduler::EveryJob "15m" {:first_in=>4}
  753569630   error:
  753569630     753569630
  753569630     ArgumentError
  753569630     header field value cannot include CR/LF

with help of google I found that downgrade of ruby (now ruby 2.5.5p157 (2019-03-15 revision 67260) [i386-linux-gnu]) but this leads me rvm and other funnies - which would be task for upcoming days. Before going this path, I'm wondering if you guys have any hints.
thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment