-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit afc3b68
Showing
42 changed files
with
4,141 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/gcal | ||
/build | ||
.autoenv* | ||
|
||
# Created by https://www.gitignore.io/api/go,sublimetext,vim | ||
|
||
### Go ### | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, build with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 | ||
.glide/ | ||
|
||
### SublimeText ### | ||
# cache files for sublime text | ||
*.tmlanguage.cache | ||
*.tmPreferences.cache | ||
*.stTheme.cache | ||
|
||
# workspace files are user-specific | ||
*.sublime-workspace | ||
|
||
# project files should be checked into the repository, unless a significant | ||
# proportion of contributors will probably not be using SublimeText | ||
# *.sublime-project | ||
|
||
# sftp configuration file | ||
sftp-config.json | ||
|
||
# Package control specific files | ||
Package Control.last-run | ||
Package Control.ca-list | ||
Package Control.ca-bundle | ||
Package Control.system-ca-bundle | ||
Package Control.cache/ | ||
Package Control.ca-certs/ | ||
Package Control.merged-ca-bundle | ||
Package Control.user-ca-bundle | ||
oscrypto-ca-bundle.crt | ||
bh_unicode_properties.cache | ||
|
||
# Sublime-github package stores a github token in this file | ||
# https://packagecontrol.io/packages/sublime-github | ||
GitHub.sublime-settings | ||
|
||
### Vim ### | ||
# swap | ||
[._]*.s[a-v][a-z] | ||
[._]*.sw[a-p] | ||
[._]s[a-v][a-z] | ||
[._]sw[a-p] | ||
# session | ||
Session.vim | ||
# temporary | ||
.netrwhist | ||
*~ | ||
# auto-generated tag files | ||
tags | ||
|
||
# End of https://www.gitignore.io/api/go,sublimetext,vim |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
The MIT License (MIT) | ||
|
||
Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net> | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
|
||
<div align="center"> | ||
<img height="128" width="128" src="./icons/icon.png"> | ||
</div> | ||
|
||
Google Calendar for Alfred | ||
========================== | ||
|
||
View Google Calendar events in [Alfred][alfred]. | ||
|
||
<!-- MarkdownTOC autolink="true" bracket="round" depth="3" autoanchor="true" --> | ||
|
||
- [Download & installation](#download--installation) | ||
- [Usage](#usage) | ||
- [Date format](#date-format) | ||
- [Configuration](#configuration) | ||
- [Licensing & thanks](#licensing--thanks) | ||
|
||
<!-- /MarkdownTOC --> | ||
|
||
|
||
<a name="download--installation"></a> | ||
Download & installation | ||
----------------------- | ||
|
||
Grab the workflow from [GitHub releases][download]. Download the `Google-Calendar-Events-X.X.alfredworkflow` file and double-click it to install. | ||
|
||
|
||
<a name="usage"></a> | ||
Usage | ||
----- | ||
|
||
When run, the workflow will open Google Calendar in your browser and ask for permission to read your calendars. If you do not grant permission, it won't work. | ||
|
||
You will also be prompted to activate some calendars (the workflow will show events from these calendars). | ||
|
||
- `gcal` — Show upcoming events. | ||
- `<query>` — Filter list of events. | ||
- `↩` — Open event in browser or day in workflow. | ||
- `⌘↩` — Open event in Google or Apple Maps (if event has a location). | ||
- `⇧` / `⌘Y` — Quicklook event details. | ||
- `today` / `tomorrow` / `yesterday` — Show events for the given day. | ||
- `<query>` / `↩` / `⌘↩` / `⇧` / `⌘Y` — As above. | ||
- `gdate [<date>]` — Show one or more dates. See below for query format. | ||
- `↩` — Show events for the given day. | ||
- `gcalconf [<query>]` — Show workflow configuration. | ||
- `Active Calendars` — Turn calendars on/off. | ||
- `↩` — Toggle calendar on/off. | ||
- `Workflow is up to Date` / `An Update is Available` — Whether a newer version of the workflow is available. | ||
- `↩` — Check for or install update. | ||
- `Open Documentation` — Open this page in your brower. | ||
- `Get Help` — Visit [the thread for this workflow][forumthread] on [AlfredForum.com][alfredforum]. | ||
- `Report Issue` — [Open an issue][issues] on GitHub. | ||
- `Clear Cached Calendars & Events` — Remove cached data. | ||
|
||
|
||
<a name="date-format"></a> | ||
### Date format ### | ||
|
||
The keyword `gdate` supports an optional date. This can be specified in a number of format: | ||
|
||
- `YYYY-MM-DD` — e.g. `2017-12-01` | ||
- `YYYYMMDD` — e.g. `20180101` | ||
- `[+|-]N[d|w]` — e.g.: | ||
- `1`, `1d` or `+1d` for tomorrow | ||
- `-1` or `-1d` for yesterday | ||
- `3w` for 21 days from now | ||
- `-4w` for 4 weeks ago | ||
|
||
<a name="configuration"></a> | ||
Configuration | ||
------------- | ||
|
||
There are a couple of options in the workflow's configuration sheet (the `[x]` button in Alfred Preferences): | ||
|
||
| Setting | Description | | ||
|--------------------|-------------------------------------------------------------------------------------------------------------| | ||
| `APPLE_MAPS` | Set to `1` to open map links in Apple Maps instead of Google Maps. | | ||
| `CALENDAR_APP` | Name of application to open Google Calendar URLs (not map URLs) in. If blank, your default browser is used. | | ||
| `EVENT_CACHE_MINS` | Number of minutes to cache event lists before updating from the server. | | ||
| `SCHEDULE_DAYS` | The number of days' events to show with the `gcal` keyword. | | ||
|
||
|
||
<a name="licensing--thanks"></a> | ||
Licensing & thanks | ||
------------------ | ||
|
||
This workflow is released under the [MIT Licence][mit]. | ||
|
||
It is heavily based on the [Google API libraries for Go][google-libs] ([BSD 3-clause licence][google-licence]) and [AwGo][awgo] libraries ([MIT][mit]), and of course, [Google Calendar][gcal]. | ||
|
||
|
||
The icons are from [Elusive Icons][elusive], [Font Awesome][awesome], [Material Icons][material], [Weather Icons][weather] (all [SIL][sil]) and [Octicons][octicons] ([MIT][mit]), via the [workflow icon generator][icongen]. | ||
|
||
|
||
[gcal]: https://calendar.google.com/calendar/ | ||
[google-libs]: https://github.com/google/google-api-go-client | ||
[google-licence]: https://github.com/google/google-api-go-client/blob/master/LICENSE | ||
[alfred]: https://alfredapp.com/ | ||
[alfredforum]: https://www.alfredforum.com/ | ||
[awgo]: https://github.com/deanishe/awgo | ||
[forumthread]: https://www.alfredforum.com/ | ||
[download]: https://github.com/deanishe/alfred-gcal/releases/latest | ||
[issues]: https://github.com/deanishe/alfred-gcal/issues | ||
[sil]: http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL | ||
[mit]: https://opensource.org/licenses/MIT | ||
[elusive]: https://github.com/aristath/elusive-iconfont | ||
[awesome]: http://fortawesome.github.io/Font-Awesome/ | ||
[material]: http://zavoloklom.github.io/material-design-iconic-font/ | ||
[octicons]: https://octicons.github.com/ | ||
[weather]: https://erikflowers.github.io/weather-icons/ | ||
[icongen]: http://icons.deanishe.net |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/bin/bash | ||
|
||
# When sourced, creates an Alfred-like environment needed by modd | ||
# and ./bin/build (which sources the file itself) | ||
|
||
# getvar <name> | Read a value from info.plist | ||
getvar() { | ||
local v="$1" | ||
/usr/libexec/PlistBuddy -c "Print :$v" info.plist | ||
} | ||
|
||
export alfred_workflow_bundleid=$( getvar "bundleid" ) | ||
export alfred_workflow_version=$( getvar "version" ) | ||
export alfred_workflow_name=$( getvar "name" ) | ||
export SCHEDULE_DAYS=$( getvar "variables:SCHEDULE_DAYS" ) | ||
export EVENT_CACHE_MINS=$( getvar "variables:EVENT_CACHE_MINS" ) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
// | ||
// Copyright (c) 2017 Dean Jackson <deanishe@deanishe.net> | ||
// | ||
// MIT Licence. See http://opensource.org/licenses/MIT | ||
// | ||
// Created on 2017-11-25 | ||
// | ||
|
||
package main | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"os" | ||
"os/exec" | ||
|
||
"golang.org/x/net/context" | ||
"golang.org/x/oauth2" | ||
"golang.org/x/oauth2/google" | ||
"google.golang.org/api/calendar/v3" | ||
) | ||
|
||
const ( | ||
authServerURL = "localhost:61432" | ||
) | ||
|
||
type response struct { | ||
code string | ||
err error | ||
} | ||
|
||
// Authenticator creates an authenticated Google API client | ||
type Authenticator struct { | ||
Secret []byte | ||
TokenFile string | ||
state string | ||
client *http.Client | ||
} | ||
|
||
// NewAuthenticator creates a new Authenticator | ||
func NewAuthenticator(tokenFile string, secret []byte) *Authenticator { | ||
return &Authenticator{Secret: secret, TokenFile: tokenFile} | ||
} | ||
|
||
// GetClient returns an authenticated Google API client | ||
func (a *Authenticator) GetClient() (*http.Client, error) { | ||
if a.client != nil { | ||
return a.client, nil | ||
} | ||
|
||
// generate CSRF token | ||
b := make([]byte, 32) | ||
_, err := rand.Read(b) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't read random bytes: %v", err) | ||
} | ||
a.state = fmt.Sprintf("%x", b) | ||
|
||
ctx := context.Background() | ||
cfg, err := google.ConfigFromJSON(a.Secret, calendar.CalendarReadonlyScope) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't load config: %v", err) | ||
} | ||
|
||
tok, err := a.tokenFromFile() | ||
if err != nil { | ||
tok, err = a.tokenFromWeb(cfg) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't get token from web: %v", err) | ||
} | ||
a.saveToken(tok) | ||
} | ||
|
||
a.client = cfg.Client(ctx, tok) | ||
return a.client, nil | ||
} | ||
|
||
// tokenFromFile loads the oauth2 token from a file | ||
func (a *Authenticator) tokenFromFile() (*oauth2.Token, error) { | ||
f, err := os.Open(a.TokenFile) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't open token file: %v", err) | ||
} | ||
tok := &oauth2.Token{} | ||
err = json.NewDecoder(f).Decode(tok) | ||
defer f.Close() | ||
return tok, err | ||
} | ||
|
||
// saveToken saves an oauth2 token to a file | ||
func (a *Authenticator) saveToken(tok *oauth2.Token) error { | ||
f, err := os.OpenFile(a.TokenFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600) | ||
if err != nil { | ||
return fmt.Errorf("couldn't open token file: %v", err) | ||
} | ||
defer f.Close() | ||
return json.NewEncoder(f).Encode(tok) | ||
} | ||
|
||
// tokenFromWeb initiates web-based authentication and retrieves the oauth2 token | ||
func (a *Authenticator) tokenFromWeb(cfg *oauth2.Config) (*oauth2.Token, error) { | ||
if err := a.openAuthURL(cfg); err != nil { | ||
return nil, fmt.Errorf("couldn't open auth URL: %v", err) | ||
} | ||
|
||
code, err := a.codeFromLocalServer() | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't get token from local server: %v", err) | ||
} | ||
|
||
tok, err := cfg.Exchange(oauth2.NoContext, code) | ||
if err != nil { | ||
return nil, fmt.Errorf("couldn't retrieve token from web: %v", err) | ||
} | ||
return tok, nil | ||
} | ||
|
||
// openAuthURL opens the Google API authentication URL in the default browser | ||
func (a *Authenticator) openAuthURL(cfg *oauth2.Config) error { | ||
authURL := cfg.AuthCodeURL(a.state, oauth2.AccessTypeOffline, oauth2.ApprovalForce) | ||
cmd := exec.Command("/usr/bin/open", authURL) | ||
if err := cmd.Run(); err != nil { | ||
return fmt.Errorf("couldn't open auth URL: %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
// codeFromLocalServer starts a local webserver to receive the oauth2 token | ||
// from Google | ||
func (a *Authenticator) codeFromLocalServer() (string, error) { | ||
c := make(chan response) | ||
srv := &http.Server{Addr: authServerURL} | ||
|
||
go func() { | ||
log.Printf("local webserver started") | ||
if err := srv.ListenAndServe(); err != nil { | ||
c <- response{err: err} | ||
} | ||
}() | ||
|
||
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { | ||
vars := req.URL.Query() | ||
code := vars.Get("code") | ||
state := vars.Get("state") | ||
log.Printf("oauth2 state=%v", state) | ||
log.Printf("oauth2 code=%s", code) | ||
|
||
// Verify state to prevent CSRF | ||
if state != a.state { | ||
c <- response{err: fmt.Errorf("state mismatch: expected=%s, got=%s", a.state, state)} | ||
io.WriteString(w, "bad state\n") | ||
return | ||
} | ||
|
||
c <- response{code: code} | ||
io.WriteString(w, "ok\n") | ||
}) | ||
|
||
r := <-c | ||
|
||
// log.Printf("srv=%+v, response=%+v", srv, r) | ||
if err := srv.Shutdown(context.Background()); err != nil { | ||
log.Printf("shutdown error: %v", err) | ||
if err != http.ErrServerClosed { | ||
return "", fmt.Errorf("local webserver error: %v", err) | ||
} | ||
} | ||
log.Printf("local webserver stopped") | ||
|
||
return r.code, r.err | ||
} |
Oops, something went wrong.