├── .gitignore ├── static ├── favicon.png ├── styles │ ├── mobile.less │ └── index.less ├── index.jade └── github-btn.html ├── bower.json ├── config.coffee ├── README.md ├── package.json ├── LICENSE └── app.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /node_modules 3 | /bower_components 4 | -------------------------------------------------------------------------------- /static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackPlan/github-commit-ical/HEAD/static/favicon.png -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-commit-ical", 3 | "dependencies": { 4 | "normalize.css": "~3.0.2" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /config.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | auth: 3 | user: 'username' 4 | pass: 'personal-access-tokens' 5 | 6 | sentry: 'sentry-api-key' 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # github-commit-ical 2 | View your commit history in Calendar 3 | 4 | ![screenshot](https://cloud.githubusercontent.com/assets/1191561/3712987/1287c420-1545-11e4-9868-bbf4ff73a6c3.png) 5 | -------------------------------------------------------------------------------- /static/styles/mobile.less: -------------------------------------------------------------------------------- 1 | @media (max-width: 767px) { 2 | h1{ 3 | font-size: 1.5em; 4 | } 5 | h2{ 6 | font-size: 1.3em; 7 | } 8 | h3{ 9 | font-size: 1.2em; 10 | } 11 | 12 | .container{ 13 | input, button{ 14 | float: none; 15 | width: 100%; 16 | margin-bottom: .5em; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-commit-ical", 3 | "version": "0.1.1", 4 | "description": "View your commit history in Calendar", 5 | "homepage": "https://commitcal.newsbee.io", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/hackplan/github-commit-ical.git" 10 | }, 11 | "contributors": [ 12 | { 13 | "name": "jysperm", 14 | "email": "jysperm@gmail.com", 15 | "url": "https://jysperm.me" 16 | } 17 | ], 18 | "scripts": { 19 | "start": "./node_modules/.bin/coffee app.coffee" 20 | }, 21 | "dependencies": { 22 | "async": "^0.9.0", 23 | "coffee-script": "^1.7.1", 24 | "express": "^4.10.0", 25 | "harp": "^0.14.0", 26 | "ical-generator": "^0.1.9", 27 | "raven": "^0.7.3", 28 | "redis": "^0.12.1", 29 | "request": "^2.47.0", 30 | "underscore": "^1.7.0" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jysperm(jysperm@gmail.com), HackPlan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /static/index.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | meta(charset="utf-8") 5 | title CommitCal 6 | link(rel='icon', href='/favicon.png', type='image/png') 7 | link(rel='stylesheet', href='/styles/index.css', type='text/css', media='screen') 8 | meta(name='viewport', content='width=device-width, initial-scale=1') 9 | body 10 | .header 11 | .container 12 | h1.title.highlight CommitCal 13 | h3 View your commit history in calendar 14 | p.star 15 | iframe(src="/github-btn.html?user=HackPlan&repo=github-commit-ical&type=watch&size=large", allowtransparency="true", frameborder="0", scrolling="0", width="72", height="30") 16 | .body 17 | .container.clearfix 18 | form.clearfix(onsubmit="subscribe()") 19 | input#username(type="text", placeholder="Enter your GitHub username") 20 | button(type="submit") Subscribe to Calendar 21 | #subscription.clearfix(style="display:none") 22 | #subscription_url 23 | .footer 24 | .container.copyright.clearfix 25 | .left 26 | p © HackPlan 27 | .right 28 | p A 29 | a(href="http://hackplan.com") Hackplan 30 | | Project 31 | script. 32 | function subscribe() { 33 | var username = document.getElementById('username').value; 34 | 35 | if (!username) { 36 | return false; 37 | } 38 | 39 | var url = "commitcal.newsbee.io/" + username; 40 | 41 | if (window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) { 42 | window.open("webcal://" + url); 43 | } 44 | 45 | var link = document.getElementById('subscription_url') 46 | link.innerHTML = "http://" + url; 47 | 48 | var subscription = document.getElementById('subscription') 49 | subscription.style.display = "block"; 50 | 51 | return false; 52 | } 53 | -------------------------------------------------------------------------------- /app.coffee: -------------------------------------------------------------------------------- 1 | express = require 'express' 2 | request = require 'request' 3 | async = require 'async' 4 | harp = require 'harp' 5 | path = require 'path' 6 | ical = require 'ical-generator' 7 | redis = require 'redis' 8 | raven = require 'raven' 9 | _ = require 'underscore' 10 | 11 | config = require './config' 12 | 13 | if config.sentry != 'sentry-api-key' 14 | sentry = new raven.Client config.sentry 15 | sentry.patchGlobal() 16 | else 17 | sentry = null 18 | 19 | redis_client = redis.createClient() 20 | 21 | sendRequest = (path, callback) -> 22 | {user, pass} = config.auth 23 | url = "https://#{user}:#{pass}@api.github.com#{path}" 24 | 25 | request url, 26 | headers: 27 | 'User-Agent': 'github-commit-ical' 28 | , callback 29 | 30 | app = express() 31 | 32 | app.use '/bower_components', express.static 'bower_components' 33 | app.use harp.mount 'static' 34 | 35 | app.get '/:username', (req, res) -> 36 | username = req.param 'username' 37 | 38 | sendRequest "/users/#{username}/events?per_page=300", (err, _res, body) -> 39 | body = JSON.parse body 40 | 41 | unless _.isArray body 42 | return res.status(404).end() 43 | 44 | events = _.filter body, (event) -> 45 | return event.type == 'PushEvent' 46 | 47 | async.map events, (event, callback) -> 48 | async.map event.payload.commits, (commit, callback) -> 49 | redis_client.get "github-commit-ical:#{commit.sha}", (err, result) -> 50 | if result 51 | result = JSON.parse result 52 | 53 | result.start = new Date result.start 54 | result.end = new Date result.end 55 | 56 | callback err, result 57 | 58 | else 59 | sendRequest "/repos/#{event.repo.name}/git/commits/#{commit.sha}", (err, _res, body) -> 60 | body = JSON.parse body 61 | 62 | if body.committer 63 | result = 64 | start: new Date body.committer.date 65 | end: new Date body.committer.date 66 | summary: "#{commit.message} (#{event.repo.name})" 67 | url: body.html_url 68 | else 69 | result = {} 70 | 71 | redis_client.set "github-commit-ical:#{commit.sha}", JSON.stringify(result), -> 72 | callback err, result 73 | 74 | , (err, result) -> 75 | callback err, result 76 | 77 | , (err, result) -> 78 | cal = ical() 79 | cal.setDomain('commitcal.newsbee.io').setName("#{username} Commit History") 80 | 81 | for commits in result 82 | for commit in commits 83 | if commit.summary 84 | cal.addEvent commit 85 | 86 | console.log "Request by: #{username}, X-RateLimit-Remaining: #{_res.headers['x-ratelimit-remaining']}" 87 | sentry?.captureMessage username 88 | 89 | res.header 'Content-Type', 'text/calendar; charset=utf-8' 90 | res.status(200).end(cal.toString()) 91 | 92 | if config.sentry != 'sentry-api-key' 93 | app.use raven.middleware.express config.sentry 94 | 95 | app.listen 19864 96 | -------------------------------------------------------------------------------- /static/styles/index.less: -------------------------------------------------------------------------------- 1 | @highdpi: ~"(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-resolution: 1.5dppx)"; 2 | 3 | .at2x(@path, @w: auto, @h: auto) { 4 | background-image: url(@path); 5 | height: @h; 6 | width: @w; 7 | @at2x_path: ~`@{path}.replace(/\.\w+$/, function(match) { return "@2x" + match; })`; 8 | @media @highdpi { 9 | background-image: url("@{at2x_path}"); 10 | background-size: @w @h; 11 | } 12 | } 13 | 14 | @import "/bower_components/normalize.css/normalize.css"; 15 | 16 | html{ 17 | font: 400 14px LucidaGrande, "Lucida Sans Unicode", sans-serif; 18 | color: #666; 19 | background: #f4f4f4; 20 | } 21 | 22 | a{ 23 | color: #388CCF; 24 | text-decoration: none; 25 | } 26 | 27 | p{ 28 | line-height: 1.5em; 29 | } 30 | 31 | .clearfix { 32 | &:before, 33 | &:after { 34 | content: " "; 35 | display: table; 36 | } 37 | &:after { 38 | clear: both; 39 | } 40 | } 41 | 42 | .star{ 43 | margin-bottom: 0; 44 | } 45 | 46 | .container { 47 | margin: 0 auto; 48 | padding: 1rem 3rem 3em; 49 | max-width: 40rem; 50 | box-sizing: border-box; 51 | } 52 | 53 | .contribute{ 54 | text-align: center; 55 | } 56 | 57 | .highlight{ 58 | color: #388CCF; 59 | } 60 | 61 | .header{ 62 | h3{ 63 | font-weight: normal; 64 | } 65 | .container{ 66 | font-size: 1.2em; 67 | text-align: center; 68 | } 69 | } 70 | 71 | .body{ 72 | h1{ 73 | font-size: 1.5em; 74 | font-weight: normal; 75 | line-height: 2em; 76 | } 77 | .container{ 78 | margin: 1rem auto 3rem; 79 | padding: 3rem; 80 | background-color: white; 81 | border-radius: 5px; 82 | } 83 | } 84 | 85 | input[type=text], input[type=password], textarea { 86 | display: block; 87 | width: 100%; 88 | height: 38px; 89 | padding: 6px 12px; 90 | font-size: 16px; 91 | line-height: 1.556; 92 | color: #2a3036; 93 | background-color: white; 94 | background-image: none; 95 | border: 1px solid #d7dbdf; 96 | border-radius: 3px; 97 | -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); 98 | box-shadow: inset 0 1px 1px rgba(0,0,0,0.075); 99 | -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 100 | -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 101 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 102 | } 103 | 104 | 105 | button, .button{ 106 | display: inline-block; 107 | margin-bottom: 0; 108 | font-weight: bold; 109 | text-align: center; 110 | vertical-align: middle; 111 | cursor: pointer; 112 | background-image: none; 113 | border: 1px solid transparent; 114 | white-space: nowrap; 115 | padding: 7px 12px; 116 | font-size: 14px; 117 | line-height: 1.556; 118 | border-radius: 3px; 119 | -webkit-user-select: none; 120 | -moz-user-select: none; 121 | -ms-user-select: none; 122 | user-select: none; 123 | color: white; 124 | background-color: #48a3ed; 125 | border-color: #3198ea; 126 | } 127 | 128 | .container{ 129 | #subscription{ 130 | margin-top: 1em; 131 | background: #5B9DD1; 132 | border-radius: 3px; 133 | padding: 0.5em 1em; 134 | color: #fff; 135 | } 136 | #subscription_url{ 137 | line-height: 2em; 138 | float: left; 139 | } 140 | #sub_link{ 141 | width: auto; 142 | background: #2173B6; 143 | border-color: #1E73B6; 144 | padding: 3px 10px; 145 | } 146 | 147 | input{ 148 | width: 57%; 149 | float: left; 150 | box-sizing: border-box; 151 | } 152 | button, .button { 153 | width: 40%; 154 | float: right; 155 | box-sizing: border-box; 156 | } 157 | } 158 | 159 | .footer{ 160 | p{ 161 | margin: 0; 162 | font-size: .9em; 163 | } 164 | .copyright{ 165 | padding: 1rem; 166 | } 167 | .left{ 168 | float: left; 169 | } 170 | .right{ 171 | float: right; 172 | } 173 | } 174 | 175 | @import "mobile"; -------------------------------------------------------------------------------- /static/github-btn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | --------------------------------------------------------------------------------