Instantly share code, notes, and snippets.

@jmb /README.md
Last active Jul 22, 2018

Embed
What would you like to do?
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;
}
}
@dannyboi0077

This comment has been minimized.

dannyboi0077 commented Feb 19, 2015

Good Morning! I'm trying to get this working, but can't quite figure it out. What do I need to add to my dashboard file? I have added:

<li data-row="1" data-col="1" data-sizex="1" data-sizey="1">
  <div data-id="google_calendar" data-view="GoogleCalendar"></div>
</li>

But no data is displayed. I just get the blank widget with "Next Event:" on it. My job file is like

service_account_email = ' EmailAddress@developer.gserviceaccount.com' # Email of service account
key_file = '/path/to/key.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key.
calendarID = 'googleaccount@gmail.com' # Calendar ID.

No errors are displayed when the job runs and I get the following output when I open the Dashboard

123.123.123.123 - - [20/Feb/2015 10:04:54] "GET /views/google_calendar.html HTTP/1.1" 200 466 0.0009

Any ideas? Thank you in advance for your help.

@dannyboi0077

This comment has been minimized.

dannyboi0077 commented Feb 19, 2015

Ok so I have figured out that I can't follow instructions. I didn't enable the Calendar API. The code I added to the dashboard file was correct.

But now the events show up but they all have the same date. The date being today.

@jmb

This comment has been minimized.

Owner

jmb commented Feb 26, 2015

Not sure offhand why it would do that - if you follow the code through I think it should just pick out the start date from each of the items that it pulls from the calendar. I'll have a go with a fresh install perhaps.

@tracstarr

This comment has been minimized.

tracstarr commented Mar 23, 2015

For those that might get the following error:
Authorization failed. Server message:
{
"error" : "invalid_grant"
}

Make sure your system time is correct. Mine was off by 30 min even though it was suppose to be set via web automatically.

@michaelgra

This comment has been minimized.

michaelgra commented Mar 27, 2015

Can anyone possibly help a newbie with the following error "google_calendar.rb:25:in `initialize': can't convert nil into String (TypeError)"

I'm pretty sure I've got the correct path to the key in with the password, not sure where I'm going wrong

@kush4all

This comment has been minimized.

kush4all commented Apr 1, 2015

This has a problem with all day events, where you don't specify start or end time. Works really good when you have events with time. I am getting today's date and time for all All day events. Please help.

@qlaffont

This comment has been minimized.

qlaffont commented May 9, 2015

It's impossible for me. I have the same problem. No data is displayed. I just get the blank widget with "Next Event:" on it. I do all your process and i fail... What is the problem ?

@kush4all

This comment has been minimized.

kush4all commented May 9, 2015

I think you are forgetting to give your service account calendar access. I had same issue. You have to share calendar with your service account. Let me know. JMB, please help me in fixing all day events. (Which don't have start and end time specified.) Thank you for making this widget with Google authentication.

@jsyeo

This comment has been minimized.

jsyeo commented May 27, 2015

I forked your gist to fetch from the calendar's private address instead. I hope you are cool with that. 😄 https://gist.github.com/jsyeo/39d3fde3afbffdd31093

@coderaver

This comment has been minimized.

coderaver commented Jul 28, 2015

Hi guys! So I've got this widget working and showing upcoming events for my calendar. Easy! However, the respective Morning/Afternoon/Evening part of events aren't showing up. Looking through the html it looks like this is the event.summary. I ended up using moment.min.js because I already had it installed for another widget. Could this have something to do with it? Code newbie here so have patience. Thank you

@PeterBi87

This comment has been minimized.

PeterBi87 commented Oct 12, 2015

I got the same problem as @michaelgra

/google_calendar.rb:25:in `initialize': can't convert nil into String (TypeError)

The path to the p12 file has to be the comple path including the dashing path or only the path out of the dashboard path? (/jobs/p12.p12 or /opt/mydashboardproject/jobs/p12.12 ? )

tried both, but it didnt fix the error.

maybe i did something wrong in here?:

Update these to match your own apps credentials

service_account_email = 'BLACKENED@developer.gserviceaccount.com' # Email of service account
key_file = '/opt/mydashboardproject/dashing-google-p12.p12' # File containing your private key
key_secret = 'notasecret' # Password to unlock private key
calendarID = 'BLACKENED%40group.calendar.google.com' # Calendar ID.

BLACKENED: i replaced the numbers and letters with BLACKENED ...

Any idea?

@mtwalsh

This comment has been minimized.

mtwalsh commented Oct 20, 2015

@coderaver How did you get this working?

I, as well as others, can't seem to get passed this error:

google_calendar.rb:25:in `initialize': no implicit conversion of nil into String (TypeError)

Were you able to resolve this?

Any advice gratefully received!

@mtwalsh

This comment has been minimized.

mtwalsh commented Oct 20, 2015

Ah, it was the path to the key file that was wrong, it should be relative to dashing's root folder (i.e. the parent folder to /jobs).

@peckeltw

This comment has been minimized.

peckeltw commented Dec 12, 2015

@kush4all

I simply added a start and end date time to every event in the ruby script:

  result = client.execute!(
                          :api_method => calendar_api.events.list,
                          :parameters => {
                          :calendarId => ENV['CALENDAR'],
                                          :timeMin => Time.now.iso8601,
                                          :orderBy => 'startTime',
                                          :singleEvents => 'true',
                                          :maxResults => 5,
                                          :showDeleted => 'false'
                                          })

  result.data.items.each do |event|

  if event.start.date_time == nil
  event.start.date_time = event.start.date + " 0:00:00 +0100"
  end

  if event.end.date_time == nil
  event.end.date_time = event.end.date + " 0:00:00 +0100"
  end

  end

All day events then are displayed all day long.

@peckeltw

This comment has been minimized.

peckeltw commented Dec 12, 2015

@kush4all forgot to include the last two lines

  send_event('google_calendar', { events: result.data })

end
@mtwalsh

This comment has been minimized.

mtwalsh commented Jan 7, 2016

Thanks for that fix @peckeltw, worked for me.

@peckeltw

This comment has been minimized.

peckeltw commented Feb 8, 2016

You're welcome, @mtwalsh!

@sebstr1

This comment has been minimized.

sebstr1 commented Feb 11, 2016

Great job with this widget!

It is working as it should except that I have special letters in my language but it is just these 3 chars: å ä ö. These 3 are very common Swedish letters. So whenever there is an event containing one of those letters the widget breaks and displays nothing with the following error from logs:

scheduler caught exception:
"\xC3" from ASCII-8BIT to UTF-8
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:129:in `to_json'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:129:in `send_event'
/opt/dashboard/jobs/google_calendar.rb:28:in `block in <top (required)>'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:232:in `call'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:232:in `trigger_block'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/rufus-scheduler-2.0.24/lib/rufus/sc/jobs.rb:206:in `block in trigger'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/rufus-scheduler-2.0.24/lib/rufus/sc/scheduler.rb:431:in `call'
/home/pi/.rbenv/versions/1.9.3-p448/lib/ruby/gems/1.9.1/gems/rufus-scheduler-2.0.24/lib/rufus/sc/scheduler.rb:431:in `block in trigger_job'

If I remove the event with one of those letters, error goes away. I've been trying to fix this for the past 2 hours with zero success. Help anyone?

Best case: The widget displays the text with the special chars (å ä ö) Worst case: The ö is converted to o and å ä to a

@jstapley

This comment has been minimized.

jstapley commented Mar 6, 2016

Has anyone seen this error before? I have been using this widget for 12 months and then I decided to move my dashboard to the cloud and I'm getting the following error when I try to start the dashboard with this widget.

`root@ubuntu-512mb-tor1-01:~/dashboard# dashing start

/var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require': /var/lib/gems/1.9.1/gems/jwt-1.5.3/lib/jwt/verify.rb:7: unknown type of %string (SyntaxError)
%i[verify_aud verify_expiration ...

/var/lib/gems/1.9.1/gems/jwt-1.5.3/lib/jwt/verify.rb:7: syntax error, unexpected $end
%i[verify_aud verify_expiration ...

from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/jwt-1.5.3/lib/jwt/decode.rb:2:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/jwt-1.5.3/lib/jwt.rb:3:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/google-api-client-0.8.2/lib/google/api_client/auth/jwt_asserter.rb:15:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/google-api-client-0.8.2/lib/google/api_client/service_account.rb:16:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/google-api-client-0.8.2/lib/google/api_client.rb:31:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /root/dashboard/jobs/google_calendar_family.rb:3:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require'
from /var/lib/gems/1.9.1/gems/backports-3.6.8/lib/backports/std_lib.rb:9:in `require_with_backports'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:157:in `block in require_glob'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:156:in `each'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:156:in `require_glob'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing/app.rb:167:in `<top (required)>'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing.rb:3:in `require'
from /var/lib/gems/1.9.1/gems/dashing-1.3.4/lib/dashing.rb:3:in `<top (required)>'
from config.ru:1:in `require'
from config.ru:1:in `block in <main>'
from /var/lib/gems/1.9.1/gems/rack-1.5.5/lib/rack/builder.rb:55:in `instance_eval'
from /var/lib/gems/1.9.1/gems/rack-1.5.5/lib/rack/builder.rb:55:in `initialize'
from config.ru:1:in `new'
from config.ru:1:in `<main>'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/rack/adapter/loader.rb:33:in `eval'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/rack/adapter/loader.rb:33:in `load'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/thin/controllers/controller.rb:182:in `load_rackup_config'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/thin/controllers/controller.rb:72:in `start'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/thin/runner.rb:200:in `run_command'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/lib/thin/runner.rb:156:in `run!'
from /var/lib/gems/1.9.1/gems/thin-1.6.4/bin/thin:6:in `<top (required)>'
from /usr/local/bin/thin:23:in `load'
from /usr/local/bin/thin:23:in `<main>'

`

@hkmaly

This comment has been minimized.

hkmaly commented Mar 17, 2016

jstapley: You need to use version 1.5.2. Version 1.5.3 is broken, see jwt/ruby-jwt#132

@n3rdz

This comment has been minimized.

n3rdz commented Apr 9, 2016

same here, how can i fix this? how can i load and use v 1.5.2 ?

@sash85

This comment has been minimized.

sash85 commented Apr 20, 2016

@n3rdz an new version is out and should work. You have to upgrade your Gemfile to ruby 1.5.4.

@sash85

This comment has been minimized.

sash85 commented Apr 20, 2016

Is it possible to limit the length of the calendar listings? My layout get broken if a entry is to long.

@dazayas

This comment has been minimized.

dazayas commented Apr 21, 2016

Suppose I wanted, for all-day events, to have the widget display "All Day" instead of start and end times of 12:00 AM. What would be the best way to implement that?

@vageesh79

This comment has been minimized.

vageesh79 commented May 4, 2016

I am not getting any error while starting dashing but in dashboard instead of calendar
capture
i am getting some template code, What i am doing wrong any ideas.

@pryonic

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

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

This comment has been minimized.

lanceDamage commented Oct 31, 2017

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

This comment has been minimized.

wmreynolds commented Mar 14, 2018

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

@emilyboda

This comment has been minimized.

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

This comment has been minimized.

rnhall82 commented Jul 22, 2018

@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.

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