├── .codeclimate.yml ├── .gcloudignore ├── .gitignore ├── LICENSE ├── README.md ├── app.yaml ├── bower.json ├── index.yaml ├── main.go ├── models.go ├── quack.yaml ├── services ├── adwebhook.go ├── bitbucket.go ├── constants.go ├── custom.go ├── doorbell.go ├── fabric.go ├── git.go ├── google_stackdriver.go ├── grafana.go ├── hipchat.go ├── jenkins_job_notify.go ├── pingdom.go ├── pushover.go ├── slack.go ├── teamcity.go ├── telegram.go ├── travis.go ├── trello.go └── utils.go ├── static ├── css │ ├── home.css │ └── styles.css └── js │ └── common.js ├── structs.go ├── templates ├── base.html ├── callback.html ├── components │ └── wh-created.html ├── index.html └── list.html └── utility.go /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | # This is a sample .codeclimate.yml configured for Engine analysis on Code 2 | # Climate Platform. For an overview of the Code Climate Platform, see here: 3 | # http://docs.codeclimate.com/article/300-the-codeclimate-platform 4 | 5 | # Under the engines key, you can configure which engines will analyze your repo. 6 | # Each key is an engine name. For each value, you need to specify enabled: true 7 | # to enable the engine as well as any other engines-specific configuration. 8 | 9 | # For more details, see here: 10 | # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform 11 | 12 | # For a list of all available engines, see here: 13 | # http://docs.codeclimate.com/article/296-engines-available-engines 14 | 15 | engines: 16 | # to turn on an engine, add it here and set enabled to `true` 17 | # to turn off an engine, set enabled to `false` or remove it 18 | golint: 19 | enabled: true 20 | checks: 21 | GoLint/Naming/MixedCaps: 22 | enabled: false 23 | GoLint/Naming/Initialisms: 24 | enabled: false 25 | gofmt: 26 | enabled: true 27 | eslint: 28 | enabled: true 29 | 30 | # Engines can analyze files and report issues on them, but you can separately 31 | # decide which files will receive ratings based on those issues. This is 32 | # specified by path patterns under the ratings key. 33 | 34 | # For more details see here: 35 | # http://docs.codeclimate.com/article/289-configuring-your-repository-via-codeclimate-yml#platform 36 | 37 | # Note: If the ratings key is not specified, this will result in a 0.0 GPA on your dashboard. 38 | 39 | # ratings: 40 | # paths: 41 | # - app/** 42 | # - lib/** 43 | # - "**.rb" 44 | # - "**.go" 45 | 46 | # You can globally exclude files from being analyzed by any engine using the 47 | # exclude_paths key. 48 | 49 | #exclude_paths: 50 | #- spec/**/* 51 | #- vendor/**/* 52 | -------------------------------------------------------------------------------- /.gcloudignore: -------------------------------------------------------------------------------- 1 | # This file specifies files that are *not* uploaded to Google Cloud Platform 2 | # using gcloud. It follows the same syntax as .gitignore, with the addition of 3 | # "#!include" directives (which insert the entries of the given .gitignore-style 4 | # file at that point). 5 | # 6 | # For more information, run: 7 | # $ gcloud topic gcloudignore 8 | # 9 | .gcloudignore 10 | # If you would like to upload your .git directory, .gitignore file or files 11 | # from your .gitignore file, remove the corresponding line 12 | # below: 13 | .git 14 | .gitignore 15 | 16 | # Binaries for programs and plugins 17 | *.exe 18 | *.exe~ 19 | *.dll 20 | *.so 21 | *.dylib 22 | # Test binary, build with `go test -c` 23 | *.test 24 | # Output of the go coverage tool, specifically when used with LiteIDE 25 | *.out -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | github.com/gorilla/context 27 | github.com/gorilla/mux 28 | bower_components/ 29 | keys.json 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Zonito 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. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://storage.googleapis.com/pgwebhook/Webhook.png)](http://webhook.co) 2 | 3 | # [webhook.co](http://webhook.co) 4 | 5 | Webhook is a method of altering and augmenting the behavior of web applications, or a web pages, using some custom callbacks. These callbacks handled, modified, managed and maintained by some third-party developers and users who may not necessarily be affiliated with the origination web application or web page. 6 | 7 | Webhook.co offers the similar service for your web based application. It simply generates a URL which can be used in all your development application. Once your application performs any action such as git push, Travis pass or Travis fail etc. then Webhook.co will push the same information to your selected services. Isn’t is simple and amazing! 8 | 9 | The popular applications which are already using web hooks include Assembla, CallMyApp, FreshBooks, Google Code, GitHub, Femtoo and PayPal etc. If you are also looking for a solution for your web hooks work then this is the right place to visit. 10 | 11 | ## Important features of website.co: 12 | 13 | Webhook.co provides a specific URL to users for various events, a web application will POST the data to those URLs when an action perform. It is very simple to use and it’s up to you and whatever you want to accomplish. Using our service, developers can create notification for themselves, real-time synchronization with other app, validate the data and also prevent it from being used by the application and process the data and repost using various APIs. 14 | 15 | Webhook.co provide hooks for most popular platforms such as [Github](https://developer.github.com/webhooks/) (only push action), [Bitbucket](https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html), [Travis](http://docs.travis-ci.com/user/notifications/#Webhook-notification), [Doorbell.io](https://doorbell.io/docs/webhooks), [TeamCity](https://www.jetbrains.com/teamcity/) and [Pingdom](https://help.pingdom.com/hc/en-us/articles/203611322-Setting-up-a-Webhook-and-an-Alerting-Endpoint). We also offer services for [Telegram](http://www.telegram.org), [Pushover](http://pushover.net), [Hipchat](http://hipchat.com) and [Trello](http://www.trello.com). The service includes three types of actions: 16 | 17 | * **Push**: is used to receive data in real time. 18 | * **Plugins**: is used to process data and return something. 19 | 20 | Webhook.co let you offer a URL which is used to access all the APIs. The same URL works for accessing all the hooks and services offered. Please do create [new issue](https://github.com/PredictionGuru/webhook/issues) for any modification, features, queries in order to make our service more flexible, durable and easy to use. 21 | 22 | 23 | ## Webhook supported 24 | 25 | * [Bitbucket](https://confluence.atlassian.com/bitbucket/manage-webhooks-735643732.html) 26 | * [Github](https://developer.github.com/webhooks/) (Only Push) 27 | * [Doorbell.io](https://doorbell.io/docs/webhooks) 28 | * [Travis](http://docs.travis-ci.com/user/notifications/#Webhook-notification) 29 | * [Pingdom](https://help.pingdom.com/hc/en-us/articles/203611322-Setting-up-a-Webhook-and-an-Alerting-Endpoint) 30 | * [TeamCity](https://www.jetbrains.com/teamcity/) 31 | * [Jenkins - Job Notification](https://wiki.jenkins-ci.org/display/JENKINS/Notification+Plugin) 32 | * Anymore? Please contribute 33 | 34 | ## Connected Services 35 | 36 | * [Trello](http://www.trello.com) 37 | * [Telegram (@WebhookCo)](http://www.telegram.org) 38 | * [Pushover](http://pushover.net) 39 | * [Hipchat](http://hipchat.com) 40 | * Pushbullet (Yet to start) 41 | * Anymore? Please contribute 42 | 43 | ## Demo 44 | 45 | * [webhook.co](http://webhook.co) 46 | 47 | ## Deploy to your Google App Engine 48 | 49 | * Clone it `git clone`. 50 | * instal and run `quack` [Quack](https://github.com/Autodesk/quack) 51 | * Install and run `bower install` 52 | * Add `services/keys.json` with respective details. 53 | ```json 54 | { 55 | "pushoverKey": "", 56 | "trelloKey": "", 57 | "trelloSecret": "", 58 | "teleToken": "" 59 | } 60 | ``` 61 | * Deploy to your GAE application (Make sure you update `app.yaml`). 62 | 63 | ## Contributing 64 | 65 | We <3 issue submissions, and address your problem as quickly as possible! 66 | 67 | If you want to write code: 68 | 69 | * Fork the repository 70 | * Create your feature branch (`git checkout -b my-new-feature`) 71 | * Commit your changes (`git commit -am 'add some feature'`) 72 | * Push to your branch (`git push origin my-new-feature`) 73 | * Create a new Pull Request 74 | 75 | 76 | [![Build Status](http://38.media.tumblr.com/7d922f7b05a10891d00543c7a4acb79d/tumblr_inline_mk24hqGq6X1qz4rgp.jpg)](http://webhook.co) 77 | 78 | **More about webhooks**: https://vimeo.com/4537957 79 | 80 | ![Analytics](https://ga-beacon.appspot.com/UA-68498210-1/webhook/repo) 81 | -------------------------------------------------------------------------------- /app.yaml: -------------------------------------------------------------------------------- 1 | # application: pgwebhook 2 | # version: 1-0-0 3 | runtime: go111 4 | 5 | handlers: 6 | - url: /bower_components 7 | static_dir: bower_components 8 | expiration: 1d 9 | - url: /components 10 | static_dir: templates/components 11 | - url: /static 12 | static_dir: static 13 | - url: / 14 | script: auto 15 | - url: /w.* 16 | script: auto 17 | - url: /telegram/.* 18 | script: auto 19 | - url: /.* 20 | script: auto 21 | login: required 22 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webhook", 3 | "version": "0.0.1", 4 | "homepage": "https://github.com/zonito/webhook", 5 | "authors": [ 6 | "Love Sharma " 7 | ], 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "iron-elements": "PolymerElements/iron-elements#~1.0.3", 18 | "paper-elements": "PolymerElements/paper-elements#~1.0.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.yaml: -------------------------------------------------------------------------------- 1 | indexes: 2 | - kind: Webhook 3 | ancestor: yes 4 | properties: 5 | - name: User 6 | - name: Date 7 | direction: desc 8 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Main file 2 | 3 | package main 4 | 5 | import ( 6 | "encoding/json" 7 | "fmt" 8 | "github.com/gorilla/mux" 9 | "github.com/webhook/services" 10 | "google.golang.org/appengine" 11 | "google.golang.org/appengine/datastore" 12 | "google.golang.org/appengine/log" 13 | "google.golang.org/appengine/user" 14 | "html/template" 15 | "net/http" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | var redirectTmpl = template.Must( 21 | template.ParseFiles("templates/callback.html")) 22 | 23 | // Initialize Appengine. 24 | // Only routes, that's it! 25 | func main() { 26 | route := mux.NewRouter() 27 | route.HandleFunc("/", root) 28 | route.HandleFunc("/cb", callback) 29 | route.HandleFunc("/connect", connect) 30 | route.HandleFunc("/created.json", createdJson) 31 | route.HandleFunc("/delete/{handler}", deleteHandler) 32 | route.HandleFunc("/login", login) 33 | route.HandleFunc("/redirect", redirect) 34 | route.HandleFunc("/save", save) 35 | route.HandleFunc("/telegram/{telegramToken}", telegramWebhook) 36 | route.HandleFunc("/trello/{type}/{boardid}", trelloList) 37 | route.HandleFunc("/w{handler}", hooks) 38 | http.Handle("/", route) 39 | appengine.Main() 40 | } 41 | 42 | // Return list of created webhooks (/created.json) 43 | func createdJson(writer http.ResponseWriter, request *http.Request) { 44 | context := appengine.NewContext(request) 45 | appUser := user.Current(context) 46 | webhooks := getWebhooks(context, appUser.Email) 47 | list, _ := json.Marshal(webhooks) 48 | writer.Header().Set("Content-Type", "application/json") 49 | fmt.Fprintf(writer, string(list)) 50 | } 51 | 52 | // Redirect use to get trello service approval. (/connect) 53 | func login(writer http.ResponseWriter, request *http.Request) { 54 | http.Redirect(writer, request, "/", http.StatusFound) 55 | } 56 | 57 | // Root handler (/), show for to create new and list of created hooks. 58 | func root(writer http.ResponseWriter, request *http.Request) { 59 | context := appengine.NewContext(request) 60 | appUser := user.Current(context) 61 | if appUser != nil { 62 | listTmpl := template.Must( 63 | template.ParseFiles("templates/base.html", "templates/list.html")) 64 | url, _ := user.LogoutURL(context, "/") 65 | data := struct { 66 | AccessToken string 67 | Logout string 68 | }{getAccessToken(context, appUser.Email), url} 69 | if err := listTmpl.Execute(writer, data); err != nil { 70 | http.Error(writer, err.Error(), http.StatusInternalServerError) 71 | } 72 | } else { 73 | homeTmpl := template.Must(template.ParseFiles("templates/index.html")) 74 | homeTmpl.Execute(writer, nil) 75 | } 76 | } 77 | 78 | // Redirect use to get trello service approval. (/connect) 79 | func connect(writer http.ResponseWriter, request *http.Request) { 80 | http.Redirect(writer, request, services.GetAuthorizeUrl(), http.StatusFound) 81 | } 82 | 83 | // Once approval from service is done, read the token, make post request 84 | // to callback handler (/cb) to save token. 85 | func redirect(writer http.ResponseWriter, request *http.Request) { 86 | if err := redirectTmpl.Execute(writer, nil); err != nil { 87 | http.Error(writer, err.Error(), http.StatusInternalServerError) 88 | } 89 | } 90 | 91 | // Callback with token in post payload. 92 | func callback(writer http.ResponseWriter, request *http.Request) { 93 | context := appengine.NewContext(request) 94 | appUser := user.Current(context) 95 | accessToken := AccessTokens{ 96 | Email: appUser.Email, 97 | AccessToken: request.FormValue("token"), 98 | } 99 | key := datastore.NewIncompleteKey( 100 | context, "AccessTokens", accessTokenKey(context, appUser.Email)) 101 | _, err := datastore.Put(context, key, &accessToken) 102 | if err != nil { 103 | http.Error(writer, err.Error(), http.StatusInternalServerError) 104 | return 105 | } 106 | http.Redirect(writer, request, "/", http.StatusFound) 107 | } 108 | 109 | // Get list of trello boards or lists. 110 | func trelloList(writer http.ResponseWriter, request *http.Request) { 111 | vars := mux.Vars(request) 112 | context := appengine.NewContext(request) 113 | appUser := user.Current(context) 114 | writer.Header().Set("Content-Type", "application/json") 115 | accessToken := getAccessToken(context, appUser.Email) 116 | if vars["type"] == "lists" { 117 | fmt.Fprintf( 118 | writer, services.GetBoardLists( 119 | context, vars["boardid"], accessToken)) 120 | return 121 | } 122 | fmt.Fprintf(writer, services.GetBoards(context, accessToken)) 123 | } 124 | 125 | // Save new hook from web. 126 | func save(writer http.ResponseWriter, request *http.Request) { 127 | context := appengine.NewContext(request) 128 | appUser := user.Current(context) 129 | response := Response{ 130 | Success: true, 131 | Reason: "", 132 | } 133 | handler := "w" + services.GetAlphaNumberic(7) 134 | webhook := Webhook{ 135 | User: appUser.Email, 136 | Handler: handler, 137 | Date: time.Now(), 138 | Count: 0, 139 | } 140 | if request.FormValue("service") == "trello" { 141 | webhook.Type = "Trello" 142 | webhook.BoardId = request.FormValue("boardId") 143 | webhook.BoardName = request.FormValue("boardName") 144 | webhook.ListId = request.FormValue("listId") 145 | webhook.ListName = request.FormValue("listName") 146 | services.PushToTrello( 147 | context, webhook.ListId, 148 | getAccessToken(context, webhook.User), "You are connected!", "") 149 | } else if request.FormValue("service") == "telegram" { 150 | webhook.Type = "Telegram" 151 | webhook.TeleChatId, webhook.TeleChatName = services.GetChatIdFromCode( 152 | context, request.FormValue("teleCode")) 153 | if webhook.TeleChatId == 0 { 154 | response.Success = false 155 | response.Reason = "Invalid code." 156 | } else { 157 | services.SendTeleMessage( 158 | context, "You are connected!", webhook.TeleChatId) 159 | } 160 | } else if request.FormValue("service") == "slack" { 161 | webhook.Type = "Slack" 162 | webhook.SlackUrl = request.FormValue("slack_url") 163 | webhook.SlackChannel = request.FormValue("slack_channel") 164 | services.SendSlackMessage( 165 | context, "You are connected!", webhook.SlackUrl, 166 | webhook.SlackChannel) 167 | } else if request.FormValue("service") == "pushover" { 168 | webhook.Type = "Pushover" 169 | webhook.POUserKey = request.FormValue("poUserkey") 170 | status := services.SendPushoverMessage( 171 | context, "You are connected!", webhook.POUserKey) 172 | if status == 0 { 173 | response.Success = false 174 | response.Reason = "Invalid key." 175 | } 176 | } else if request.FormValue("service") == "hipchat" { 177 | webhook.Type = "Hipchat" 178 | webhook.HCToken = request.FormValue("hcToken") 179 | webhook.HCRoomId = request.FormValue("hcRoomid") 180 | status := services.SendHipchatMessage( 181 | context, "You are connected!", webhook.HCRoomId, 182 | webhook.HCToken, "green") 183 | if !status { 184 | response.Success = false 185 | response.Reason = "Invalid room id or token." 186 | } 187 | } 188 | if response.Success { 189 | key := datastore.NewIncompleteKey( 190 | context, "Webhook", webhookKey(context, handler)) 191 | _, err := datastore.Put(context, key, &webhook) 192 | if err != nil { 193 | log.Infof(context, "%v", err.Error()) 194 | return 195 | } 196 | response.Handler = handler 197 | } 198 | writer.Header().Set("Content-Type", "application/json") 199 | resp, _ := json.Marshal(response) 200 | fmt.Fprintf(writer, string(resp)) 201 | } 202 | 203 | func deleteHandler(writer http.ResponseWriter, request *http.Request) { 204 | vars := mux.Vars(request) 205 | context := appengine.NewContext(request) 206 | appUser := user.Current(context) 207 | response := Response{ 208 | Success: false, 209 | Reason: "Not found", 210 | } 211 | webhook := getWebhookFromHandler(context, vars["handler"]) 212 | if webhook != nil && webhook.User == appUser.Email { 213 | response.Success = true 214 | response.Reason = "" 215 | deleteWebhookFromHandler(context, vars["handler"]) 216 | } 217 | writer.Header().Set("Content-Type", "application/json") 218 | resp, _ := json.Marshal(response) 219 | fmt.Fprintf(writer, string(resp)) 220 | } 221 | 222 | // Telegram webhook 223 | func telegramWebhook(writer http.ResponseWriter, request *http.Request) { 224 | vars := mux.Vars(request) 225 | context := appengine.NewContext(request) 226 | decoder := json.NewDecoder(request.Body) 227 | fmt.Fprintf( 228 | writer, services.Telegram(context, decoder, vars["telegramToken"])) 229 | } 230 | 231 | // Actual webhook handler, receive events and post it to connected services. 232 | func hooks(writer http.ResponseWriter, request *http.Request) { 233 | vars := mux.Vars(request) 234 | handler := "w" + vars["handler"] 235 | context := appengine.NewContext(request) 236 | webhook := getWebhookFromHandler(context, handler) 237 | if webhook != nil { 238 | event, desc := services.GetEventData(request) 239 | log.Infof(context, "%s: %s \n %s", webhook.Type, event, desc) 240 | if event != "" { 241 | if webhook.Type == "Trello" { 242 | services.PushToTrello( 243 | context, webhook.ListId, 244 | getAccessToken(context, webhook.User), event, desc) 245 | } else if webhook.Type == "Telegram" { 246 | event = strings.Replace(event, "_", "\\_", -1) 247 | desc = strings.Replace(desc, "_", "\\_", -1) 248 | services.SendTeleMessage( 249 | context, event+"\n"+desc, webhook.TeleChatId) 250 | } else if webhook.Type == "Pushover" { 251 | services.SendPushoverMessage( 252 | context, event+"\n"+desc, webhook.POUserKey) 253 | } else if webhook.Type == "Slack" { 254 | services.SendSlackMessage( 255 | context, event+"\n"+desc, webhook.SlackUrl, 256 | webhook.SlackChannel) 257 | } else if webhook.Type == "Hipchat" { 258 | color := "red" 259 | if strings.Index(event, " success ") > -1 || 260 | strings.Index(event, " merged ") > -1 || 261 | strings.Index(event, " up") > -1 || 262 | strings.Index(event, "Ping!") > -1 { 263 | color = "green" 264 | } else if strings.Index(event, " pull ") > -1 { 265 | color = "yellow" 266 | } 267 | services.SendHipchatMessage( 268 | context, event+"\n"+desc, webhook.HCRoomId, 269 | webhook.HCToken, color) 270 | } 271 | } 272 | fmt.Fprintf(writer, "OK") 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /models.go: -------------------------------------------------------------------------------- 1 | // Datastore models. 2 | 3 | package main 4 | 5 | import "time" 6 | 7 | type AccessTokens struct { 8 | Email string 9 | AccessToken string // Access token of Trello 10 | } 11 | 12 | type Webhook struct { 13 | Handler string `json:"handler"` 14 | User string `json:"-"` 15 | Type string `json:"type"` 16 | BoardId string `json:"board_id"` 17 | BoardName string `json:"board_name"` 18 | ListId string `json:"list_id"` 19 | ListName string `json:"list_name"` 20 | TeleChatId int `json:"tele_chat_id"` 21 | TeleChatName string `json:"tele_name"` 22 | POUserKey string `json:"-"` 23 | HCToken string `json:"-"` 24 | HCRoomId string `json:"room"` 25 | Date time.Time `json:"date"` 26 | Count int `json:"count"` 27 | SlackUrl string `json:"slack_url"` 28 | SlackChannel string `json:"slack_channel"` 29 | } 30 | -------------------------------------------------------------------------------- /quack.yaml: -------------------------------------------------------------------------------- 1 | name: Webhook 2 | gitignore: true 3 | 4 | modules: 5 | github.com/gorilla/mux: 6 | repository: https://github.com/gorilla/mux 7 | github.com/gorilla/context: 8 | repository: https://github.com/gorilla/context 9 | 10 | profiles: 11 | init: 12 | tasks: ['modules'] 13 | -------------------------------------------------------------------------------- /services/adwebhook.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // AD Webhooks structs 8 | 9 | type ADScope struct { 10 | application string 11 | } 12 | 13 | type ADHook struct { 14 | HookId string 15 | Tenant string 16 | CallbackUrl string 17 | CreatedBy string 18 | EventType string 19 | CreatedDate string 20 | SysType string 21 | CreatorType string 22 | Status string 23 | Scope ADScope 24 | Urn string 25 | __self__ string 26 | } 27 | 28 | type ADClientPayload struct { 29 | ClientId string 30 | AppId string 31 | } 32 | 33 | type ADPayload struct { 34 | ResourceUrn string 35 | Payload ADClientPayload 36 | Hook ADHook 37 | } 38 | 39 | // Return AD data. 40 | func getADData(decoder *json.Decoder) (string, string) { 41 | var dEvent ADPayload 42 | decoder.Decode(&dEvent) 43 | event := dEvent.Hook.CreatorType + " Delete: `" + dEvent.Hook.Status + "`" 44 | desc := "Delete Client: `" + dEvent.ResourceUrn + 45 | "`\nURN: `" + dEvent.Hook.Urn + 46 | "`\nTenant: `" + dEvent.Hook.Tenant + 47 | "`\nHookID: `" + dEvent.Hook.HookId + 48 | "`\nAppID: `" + dEvent.Payload.AppId + "`" 49 | return event, desc 50 | } 51 | -------------------------------------------------------------------------------- /services/bitbucket.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // Bitbucket 10 | 11 | type BBActor struct { 12 | Username string 13 | Display_name string 14 | Uuid string 15 | Links string 16 | } 17 | 18 | type BBRepository struct { 19 | Name string 20 | Full_name string 21 | Uuid string 22 | Links BBLinks 23 | Scm string 24 | Is_private bool 25 | } 26 | 27 | type BBAuthor struct { 28 | User User 29 | Raw string 30 | } 31 | 32 | type BBLink struct { 33 | Href string 34 | } 35 | 36 | type BBLinks struct { 37 | Html BBLink 38 | Diff BBLink 39 | Self BBLink 40 | } 41 | 42 | type BBCommits struct { 43 | Message string 44 | Hash string 45 | Type string 46 | Date string 47 | Author BBAuthor 48 | } 49 | 50 | type BBNew struct { 51 | Type string 52 | Name string 53 | Target BBCommits 54 | Parents BBCommits 55 | } 56 | 57 | type BBChanges struct { 58 | Commits []BBCommits 59 | New BBNew 60 | Old BBNew 61 | Created bool 62 | Forced bool 63 | Closed bool 64 | } 65 | 66 | type BBPush struct { 67 | Changes []BBChanges 68 | } 69 | 70 | type BBContent struct { 71 | Raw string 72 | Markup string 73 | Html string 74 | } 75 | 76 | type BBInline struct { 77 | To int 78 | From int 79 | Path string 80 | } 81 | 82 | type BBParent struct { 83 | Id int 84 | } 85 | 86 | type BBComment struct { 87 | Id int 88 | Parent BBParent 89 | Content BBContent 90 | Inline BBInline 91 | Created_on string 92 | Updated_on string 93 | Links BBLinks 94 | } 95 | 96 | type BBBranch struct { 97 | Name string 98 | } 99 | 100 | type BBSource struct { 101 | Branch BBBranch 102 | Commit BBCommits 103 | Repository BBRepository 104 | } 105 | 106 | type BBPullrequest struct { 107 | Id int 108 | Title string 109 | Description string 110 | State string 111 | Author string 112 | Source BBSource 113 | Destination BBSource 114 | Merge_commit BBCommits 115 | Participants []BBActor 116 | Reviewers []BBActor 117 | Close_source_branch bool 118 | Closed_by BBActor 119 | Reason string 120 | Created_on string 121 | Updated_on string 122 | Links BBLinks 123 | } 124 | 125 | type BBApproval struct { 126 | Date string 127 | User BBActor 128 | } 129 | 130 | type BBPayload struct { 131 | Repository BBRepository 132 | Push BBPush 133 | Actor BBActor 134 | Fork BBRepository 135 | Comment BBComment 136 | Commit BBCommits 137 | Pullrequest BBPullrequest 138 | Approval BBApproval 139 | } 140 | 141 | // Return bitbucket data. 142 | func getBitbucketData(decoder *json.Decoder, eType string) (string, string) { 143 | var bEvent BBPayload 144 | decoder.Decode(&bEvent) 145 | action := strings.Split(eType, ":") 146 | event, desc := "", "" 147 | who := bEvent.Actor.Username + " (" + bEvent.Actor.Display_name + ")" 148 | if action[0] == "repo" { 149 | switch action[1] { 150 | case "push": 151 | event = bEvent.Repository.Name + ": Push Event" 152 | if len(bEvent.Push.Changes) > 0 { 153 | change := bEvent.Push.Changes[0] 154 | desc = "Who: " + who + "\nCommits:\n" 155 | for i := 0; i < len(change.Commits); i++ { 156 | desc += "\n- " + change.Commits[i].Message + 157 | " (" + change.Commits[i].Hash + ")" + 158 | "\n * " + change.Commits[i].Author.Raw 159 | } 160 | } 161 | case "fork": 162 | event = bEvent.Repository.Name + ": Fork Event" 163 | desc = "\n" + who + " Forked." 164 | case "commit_comment_created": 165 | event = bEvent.Repository.Name + ": Commit Comment Created" 166 | desc = who + " commented on " + bEvent.Commit.Hash 167 | desc += "\n Comment: " + bEvent.Comment.Content.Raw 168 | desc += "\n File: " + bEvent.Comment.Inline.Path 169 | } 170 | } else if action[0] == "pullrequest" { 171 | desc = "Description: " + bEvent.Pullrequest.Description 172 | desc += "\n From Repository: " + 173 | bEvent.Pullrequest.Source.Repository.Name 174 | switch action[1] { 175 | case "created": 176 | desc += "\n Created by: " + who 177 | case "updated": 178 | desc += "\n Updated by: " + who 179 | case "approved": 180 | desc += "\n Approved by: " + bEvent.Approval.User.Username 181 | case "unapproved": 182 | desc += "\n Unapproved by: " + bEvent.Approval.User.Username 183 | case "fulfilled": 184 | desc += "\n Merged by: " + who 185 | desc += "\n **Merged**" 186 | case "rejected": 187 | desc += "\n Rejected by: " + who 188 | desc += "\n **Rejected** because " + bEvent.Pullrequest.Reason 189 | case "comment_created": 190 | desc += "\n Commented by: " + who 191 | desc += "\n Comment: " + bEvent.Comment.Content.Raw 192 | desc += "\n File: " + bEvent.Comment.Inline.Path + " at line " + 193 | strconv.Itoa(bEvent.Comment.Inline.To) 194 | case "comment_updated": 195 | desc += "\n Comment updated by: " + who 196 | desc += "\n File: " + bEvent.Comment.Inline.Path + " at line " + 197 | strconv.Itoa(bEvent.Comment.Inline.To) 198 | case "comment_deleted": 199 | desc += "\n Comment deleted by: " + who 200 | desc += "\n File: " + bEvent.Comment.Inline.Path + " at line " + 201 | strconv.Itoa(bEvent.Comment.Inline.To) 202 | } 203 | event = bEvent.Repository.Name + ": Pull request " + action[1] + ": " + 204 | bEvent.Pullrequest.Title + " (" + bEvent.Pullrequest.State + ")" 205 | } 206 | return event, desc 207 | } 208 | -------------------------------------------------------------------------------- /services/constants.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "io/ioutil" 6 | ) 7 | 8 | type Config struct { 9 | PushoverKey string 10 | TrelloKey string 11 | TrelloSecret string 12 | TeleToken string 13 | } 14 | 15 | func getConfig() Config { 16 | content, _ := ioutil.ReadFile("services/keys.json") 17 | var conf Config 18 | json.Unmarshal(content, &conf) 19 | return conf 20 | } 21 | 22 | var config = getConfig() 23 | 24 | var pushoverKey = config.PushoverKey 25 | var trelloKey = config.TrelloKey 26 | var trelloSecret = config.TrelloSecret 27 | var teleToken = config.TeleToken 28 | -------------------------------------------------------------------------------- /services/custom.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type CMessage struct { 8 | Message string `json:"message"` 9 | } 10 | 11 | func getCustomData(decoder *json.Decoder) (string, string) { 12 | var cEvent CMessage 13 | decoder.Decode(&cEvent) 14 | event := cEvent.Message 15 | return event, "" 16 | } 17 | -------------------------------------------------------------------------------- /services/doorbell.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Doorbell structs 8 | 9 | type DBApplication struct { 10 | Name string 11 | } 12 | 13 | type DBData struct { 14 | Email string 15 | Url string 16 | member User 17 | User_Agent string 18 | Message string 19 | Sentiment string 20 | Application DBApplication 21 | Created string 22 | } 23 | 24 | type DBPayload struct { 25 | Event string 26 | Data DBData 27 | } 28 | 29 | // Return doorbell data. 30 | func getDoorbellData(decoder *json.Decoder) (string, string) { 31 | var dEvent DBPayload 32 | decoder.Decode(&dEvent) 33 | data := dEvent.Data 34 | event := data.Application.Name + " --> " + 35 | data.Sentiment + " feedback - from " + data.Email 36 | desc := data.Message + "\n\n User Agent: " + 37 | data.User_Agent + "\n\n Reply: " + data.Url 38 | return event, desc 39 | } 40 | -------------------------------------------------------------------------------- /services/fabric.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | type FBPayloadApp struct { 9 | Name string 10 | Bundle_identifier string 11 | Platform string 12 | } 13 | 14 | type FBPayload struct { 15 | Display_id int 16 | Title string 17 | Method string 18 | Crashes_count int 19 | Impacted_devices_count int 20 | Impact_level int 21 | Url string 22 | App FBPayloadApp 23 | } 24 | 25 | type FBMessage struct { 26 | Event string 27 | Payload_type string 28 | Payload FBPayload 29 | } 30 | 31 | func getFabricData(decoder *json.Decoder) (string, string) { 32 | var fbEvent FBMessage 33 | decoder.Decode(&fbEvent) 34 | event := "`Crashlytics: " + fbEvent.Payload_type + ", " + 35 | fbEvent.Payload.Title + " for " + fbEvent.Payload.App.Bundle_identifier + "`" 36 | payload := fbEvent.Payload 37 | desc := fbEvent.Event + 38 | "\nCrashes Count: " + strconv.Itoa(payload.Crashes_count) + 39 | "\nPlatform: " + payload.App.Platform + 40 | "\nName: " + payload.App.Name + 41 | "\nMethod: " + payload.Method + 42 | "\nURL: " + payload.Url + 43 | "\nMethod: " + payload.Method + 44 | "\nImpacted Devices Count: " + strconv.Itoa(payload.Impacted_devices_count) + 45 | "\nImpacted Level: " + strconv.Itoa(payload.Impact_level) 46 | return event, desc 47 | } 48 | -------------------------------------------------------------------------------- /services/git.go: -------------------------------------------------------------------------------- 1 | // Package services provides Github Integration. 2 | 3 | package services 4 | 5 | import ( 6 | "encoding/json" 7 | ) 8 | 9 | // Git structs 10 | 11 | type User struct { 12 | Name string 13 | Email string 14 | Username string 15 | Display_name string 16 | } 17 | 18 | type GitRepository struct { 19 | Id int 20 | Name string 21 | Full_name string 22 | Url string 23 | AbsoluteUrl string 24 | Owner User 25 | Pusher User 26 | } 27 | 28 | type GitCommit struct { 29 | Id string 30 | Message string 31 | Timestamp string 32 | Url string 33 | Author User 34 | Committer User 35 | Modified []string 36 | } 37 | 38 | type GitUser struct { 39 | Login string 40 | Id int 41 | Avatar_url string 42 | Type string 43 | Site_admin bool 44 | } 45 | 46 | type GitPullRequest struct { 47 | Url string 48 | Html_url string 49 | Id int 50 | State string 51 | Title string 52 | User GitUser 53 | Body string 54 | Repo GitRepository 55 | Merged bool 56 | Merged_by GitUser 57 | } 58 | 59 | type GitPayload struct { 60 | Zen string 61 | Ref string 62 | Compare string 63 | Repository GitRepository 64 | Commits []GitCommit 65 | Action string 66 | Number int 67 | Pull_request GitPullRequest 68 | Pusher User 69 | } 70 | 71 | // Return github data. 72 | func getGithubData(decoder *json.Decoder, header string) (string, string) { 73 | var gEvent GitPayload 74 | decoder.Decode(&gEvent) 75 | var event, desc string 76 | if header == "push" { 77 | event = gEvent.Repository.Name + " --> " + header + " event" 78 | repo := gEvent.Repository 79 | desc = repo.Name + ": \n" + 80 | "\nName: " + repo.Name + 81 | "\nUrl: " + repo.Url + 82 | "\nOwner: " + repo.Owner.Email + 83 | "\nCompare: " + gEvent.Compare + 84 | "\nRef: " + gEvent.Ref + 85 | "\nModified files\n" 86 | for i := 0; i < len(gEvent.Commits); i++ { 87 | commit := gEvent.Commits[i] 88 | desc += "\n* " + commit.Message + " (" + commit.Timestamp + ")" 89 | for j := 0; j < len(commit.Modified); j++ { 90 | desc += "\n * " + commit.Modified[j] 91 | } 92 | } 93 | } else if header == "pull_request" { 94 | pr := gEvent.Pull_request 95 | if gEvent.Action == "opened" { 96 | event = "New pull request for " + gEvent.Repository.Full_name + 97 | " from " + pr.User.Login 98 | } else if gEvent.Action == "closed" && pr.Merged { 99 | event = "Pull request merged by " + pr.Merged_by.Login 100 | } 101 | desc = "Title: " + pr.Title 102 | if pr.Body != "" { 103 | desc += "\nDescription: " + pr.Body 104 | } 105 | desc += "\nReview at " + pr.Html_url 106 | } else if gEvent.Zen != "" { 107 | event = "Ping! from " + gEvent.Repository.Name 108 | } 109 | return event, desc 110 | } 111 | -------------------------------------------------------------------------------- /services/google_stackdriver.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type SDIncident struct { 8 | Incident_id string 9 | Resource_id string 10 | Resource_name string 11 | State string 12 | Started_at int 13 | Ended_at int 14 | Policy_name string 15 | Condition_name string 16 | Url string 17 | Summary string 18 | } 19 | 20 | type SDMessage struct { 21 | Incident SDIncident 22 | Version int 23 | } 24 | 25 | func getStackDriverData(decoder *json.Decoder) (string, string) { 26 | var sdnEvent SDMessage 27 | decoder.Decode(&sdnEvent) 28 | event := "StackDriver: " + sdnEvent.Incident.Policy_name + 29 | ", Condition: " + sdnEvent.Incident.Condition_name 30 | desc := "URL: " + sdnEvent.Incident.Url + 31 | "\nSummary: " + sdnEvent.Incident.Summary + 32 | "\nState: " + sdnEvent.Incident.State + 33 | "\nResource: " + sdnEvent.Incident.Resource_name 34 | return event, desc 35 | } 36 | -------------------------------------------------------------------------------- /services/grafana.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | // Grafana structs 8 | 9 | type EvalMatch struct { 10 | Value int 11 | Metric string 12 | Tags []string 13 | } 14 | 15 | type GrafanaPayload struct { 16 | EvalMatches []EvalMatch 17 | ImageUrl string 18 | Message string 19 | RuleId int 20 | RuleName string 21 | RuleUrl string 22 | State string 23 | Title string 24 | } 25 | 26 | // Return grafana data. 27 | func getGrafanaData(decoder *json.Decoder) (string, string) { 28 | var gfEvent GrafanaPayload 29 | decoder.Decode(&gfEvent) 30 | return gfEvent.Title, gfEvent.Message 31 | } 32 | -------------------------------------------------------------------------------- /services/hipchat.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "google.golang.org/appengine/log" 8 | "google.golang.org/appengine/urlfetch" 9 | "net/url" 10 | ) 11 | 12 | type hCRequest struct { 13 | Color string `json:"color"` 14 | Message string `json:"message"` 15 | Notify bool `json:"notify"` 16 | Message_format string `json:"message_format"` 17 | } 18 | 19 | // Send telegram message 20 | func SendHipchatMessage( 21 | context context.Context, text string, room_id string, token string, 22 | color string) bool { 23 | uri := "https://api.hipchat.com/v2/room/" + room_id 24 | uri += "/notification?auth_token=" + url.QueryEscape(token) 25 | client := urlfetch.Client(context) 26 | payload := &hCRequest{ 27 | Color: color, 28 | Message: text, 29 | Notify: true, 30 | Message_format: "text", 31 | } 32 | str, _ := json.Marshal(payload) 33 | log.Infof(context, uri) 34 | log.Infof(context, string(str)) 35 | resp, err := client.Post( 36 | uri, "application/json", bytes.NewBuffer(str)) 37 | if err != nil { 38 | log.Infof(context, "Hipchat client.Post: %v", err.Error()) 39 | return false 40 | } 41 | defer resp.Body.Close() 42 | log.Infof(context, "response Headers:", resp.Header) 43 | log.Infof(context, "HC Status: %s", resp.Status) 44 | if resp.Status == "204 No Content" { 45 | return true 46 | } 47 | return false 48 | } 49 | -------------------------------------------------------------------------------- /services/jenkins_job_notify.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | type JJNScm struct { 9 | Url string 10 | Branch string 11 | Commit string 12 | } 13 | 14 | type JJNBuild struct { 15 | Full_url string 16 | Number int 17 | Queue_id int 18 | Phase string 19 | Status string 20 | Url string 21 | Scm JJNScm 22 | } 23 | 24 | type JJNMessage struct { 25 | Name string 26 | Url string 27 | Build JJNBuild 28 | Log string 29 | } 30 | 31 | func getJenkinsJobNoficationData(decoder *json.Decoder) (string, string) { 32 | var jjnEvent JJNMessage 33 | decoder.Decode(&jjnEvent) 34 | event := "Jenkins Job Notifier: " + jjnEvent.Name + ", Phase: " + jjnEvent.Build.Phase 35 | if jjnEvent.Build.Phase != "STARTED" { 36 | event += " (" + jjnEvent.Build.Status + ")" 37 | } 38 | desc := "URL: " + jjnEvent.Build.Full_url + 39 | "\nBuild Number: " + strconv.Itoa(jjnEvent.Build.Number) 40 | return event, desc 41 | } 42 | -------------------------------------------------------------------------------- /services/pingdom.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | type PDMessage struct { 9 | Action string 10 | Check string 11 | Checkname string 12 | Description string 13 | Host string 14 | IncidentId int 15 | } 16 | 17 | func getPingdomData(decoder *json.Decoder) (string, string) { 18 | var pdEvent PDMessage 19 | decoder.Decode(&pdEvent) 20 | event := "Pingdom: " + pdEvent.Host + " " + pdEvent.Description 21 | desc := "More Info\nCheckname: " + pdEvent.Checkname + 22 | "\nCheck: " + pdEvent.Check + 23 | "\nIncident Id: " + strconv.Itoa(pdEvent.IncidentId) + 24 | "\nAction: " + pdEvent.Action 25 | return event, desc 26 | } 27 | -------------------------------------------------------------------------------- /services/pushover.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "google.golang.org/appengine/log" 8 | "google.golang.org/appengine/urlfetch" 9 | "net/url" 10 | ) 11 | 12 | // Send push over notification message 13 | func SendPushoverMessage( 14 | context context.Context, message string, userKey string) int { 15 | apiUrl := "https://api.pushover.net/1/messages.json" 16 | parameters := url.Values{} 17 | parameters.Add("token", pushoverKey) 18 | parameters.Add("user", userKey) 19 | parameters.Add("message", message) 20 | parameters.Add("priority", "1") 21 | client := urlfetch.Client(context) 22 | resp, _ := client.Post( 23 | apiUrl, "application/x-www-form-urlencoded", 24 | bytes.NewBuffer([]byte(parameters.Encode()))) 25 | defer resp.Body.Close() 26 | log.Infof(context, "response Headers:", resp.Header) 27 | decoder := json.NewDecoder(resp.Body) 28 | pEvent := struct { 29 | Status int 30 | }{0} 31 | decoder.Decode(&pEvent) 32 | return pEvent.Status 33 | } 34 | -------------------------------------------------------------------------------- /services/slack.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package services provides Telegram Integration (https://telegram.org/). 3 | This is service to interact with / to notify. 4 | */ 5 | package services 6 | 7 | import ( 8 | "bytes" 9 | "context" 10 | "encoding/json" 11 | "google.golang.org/appengine/log" 12 | "google.golang.org/appengine/urlfetch" 13 | ) 14 | 15 | type slackRequest struct { 16 | Text string `json:"text"` 17 | Channel string `json:"channel"` 18 | } 19 | 20 | // SendTeleMessage Send telegram message 21 | func SendSlackMessage( 22 | context context.Context, text string, slack_url string, 23 | channel string) bool { 24 | log.Infof(context, "%s", slack_url) 25 | client := urlfetch.Client(context) 26 | payload := &slackRequest{ 27 | Text: text, 28 | Channel: channel, 29 | } 30 | str, _ := json.Marshal(payload) 31 | log.Infof(context, slack_url) 32 | log.Infof(context, string(str)) 33 | resp, err := client.Post( 34 | slack_url, "application/json", bytes.NewBuffer(str)) 35 | if err != nil { 36 | log.Infof(context, "Slack client.Post: %v", err.Error()) 37 | return false 38 | } 39 | defer resp.Body.Close() 40 | log.Infof(context, "response Headers:", resp.Header) 41 | log.Infof(context, "Slack Status: %s", resp.Status) 42 | if resp.Status == "200 OK" { 43 | return true 44 | } 45 | return false 46 | } 47 | -------------------------------------------------------------------------------- /services/teamcity.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type TCBuild struct { 8 | BuildStatus string 9 | BuildResult string 10 | BuildResultPrevious string 11 | BuildResultDetalta string 12 | NotifyType string 13 | BuildFullName string 14 | BuildName string 15 | BuildId string 16 | BuildTypeId string 17 | BuildInternalTypeId string 18 | BuildExternalTypeId string 19 | BuildStatusUrl string 20 | BuildStatusHtml string 21 | RootUrl string 22 | ProjectName string 23 | ProjectId string 24 | ProjectInternalId string 25 | ProjectExternalId string 26 | BuildNumber string 27 | AgentName string 28 | AgentOs string 29 | AgentHostname string 30 | TriggeredBy string 31 | Message string 32 | Text string 33 | BuildStateDescription string 34 | } 35 | 36 | type TCPayload struct { 37 | Build TCBuild 38 | } 39 | 40 | // Return teamcity data. 41 | func getTeamcityData(decoder *json.Decoder) (string, string) { 42 | var tcEvent TCPayload 43 | decoder.Decode(&tcEvent) 44 | build := tcEvent.Build 45 | event := build.ProjectName + ": " + 46 | build.BuildResult + " (Previous: " + build.BuildResultPrevious + ") " 47 | desc := build.Message + "\nBuild Status Url: " + 48 | build.BuildStatusUrl 49 | return event, desc 50 | } 51 | -------------------------------------------------------------------------------- /services/telegram.go: -------------------------------------------------------------------------------- 1 | /* 2 | Package services provides Telegram Integration (https://telegram.org/). 3 | This is service to interact with / to notify. 4 | */ 5 | package services 6 | 7 | import ( 8 | "context" 9 | "encoding/json" 10 | "google.golang.org/appengine/datastore" 11 | "google.golang.org/appengine/log" 12 | "google.golang.org/appengine/urlfetch" 13 | "net/url" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | var apiURL = "https://api.telegram.org/bot" + teleToken + "/sendMessage" 20 | 21 | // TeleVerify model is a temporary database to store code from bot. 22 | type teleVerify struct { 23 | ChatId int 24 | Code string 25 | Date time.Time 26 | Name string 27 | } 28 | 29 | // teleUser is part of teleMessage to know, who messaged. 30 | type teleUser struct { 31 | Id int 32 | First_name string 33 | Last_name string 34 | Username string 35 | Title string 36 | } 37 | 38 | // teleMessage is a message sent in telegram. 39 | type teleMessage struct { 40 | Message_id int 41 | Date int 42 | Text string 43 | From teleUser 44 | Chat teleUser 45 | New_chat_title string 46 | } 47 | 48 | // telePayload is a request body from telegram. 49 | type telePayload struct { 50 | Update_id int 51 | Message teleMessage 52 | } 53 | 54 | // Return TeleVerify datastore key. 55 | func teleVerifyKey(context context.Context, code string) *datastore.Key { 56 | return datastore.NewKey(context, "teleVerify", code, 0, nil) 57 | } 58 | 59 | // GetChatIdFromCode Return Chat id from Code 60 | func GetChatIdFromCode(context context.Context, code string) (int, string) { 61 | query := datastore.NewQuery("teleVerify").Ancestor( 62 | teleVerifyKey(context, code)).Limit(1) 63 | teleVerify := make([]teleVerify, 0, 1) 64 | keys, _ := query.GetAll(context, &teleVerify) 65 | if len(teleVerify) > 0 { 66 | chatId, name := teleVerify[0].ChatId, teleVerify[0].Name 67 | datastore.Delete(context, keys[0]) 68 | return chatId, name 69 | } 70 | return 0, "" 71 | } 72 | 73 | // SendTeleMessage Send telegram message 74 | func SendTeleMessage(context context.Context, text string, chat_id int) { 75 | uri := apiURL + "?parse_mode=Markdown&disable_web_page_preview=true" 76 | uri += "&chat_id=" + strconv.Itoa(chat_id) 77 | uri += "&text=" + url.QueryEscape(text) 78 | log.Infof(context, "%s", uri) 79 | client := urlfetch.Client(context) 80 | resp, _ := client.Get(uri) 81 | defer resp.Body.Close() 82 | } 83 | 84 | // Telegram webhook 85 | func Telegram( 86 | context context.Context, decoder *json.Decoder, token string) string { 87 | if token != teleToken { 88 | return "!OK" 89 | } 90 | var teleEvent telePayload 91 | decoder.Decode(&teleEvent) 92 | message := teleEvent.Message 93 | if strings.Index(message.Text, "/getcode") > -1 { 94 | code := GetAlphaNumberic(6) 95 | teleVerify := teleVerify{ 96 | ChatId: message.Chat.Id, 97 | Code: code, 98 | Date: time.Now(), 99 | Name: message.Chat.First_name, 100 | } 101 | if message.Chat.Id < 0 { 102 | teleVerify.Name = message.Chat.Title 103 | } 104 | key := datastore.NewIncompleteKey( 105 | context, "teleVerify", teleVerifyKey(context, code)) 106 | datastore.Put(context, key, &teleVerify) 107 | SendTeleMessage(context, code, message.Chat.Id) 108 | } else if strings.Index(message.Text, "/start") > -1 { 109 | SendTeleMessage( 110 | context, "Welcome! Next step is to get registered with webhook.co", 111 | message.Chat.Id) 112 | } else if strings.Index(message.Text, "/help") > -1 { 113 | SendTeleMessage( 114 | context, "Get registered with webhook.co", message.Chat.Id) 115 | } 116 | return "OK" 117 | } 118 | -------------------------------------------------------------------------------- /services/travis.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "encoding/json" 5 | "strconv" 6 | ) 7 | 8 | // Travis 9 | 10 | type TRRepository struct { 11 | Id int 12 | Name string 13 | Owner_name string 14 | Url string 15 | } 16 | 17 | type TRPayload struct { 18 | Author_email string 19 | Author_name string 20 | Branch string 21 | Build_url string 22 | Commit string 23 | Committed_at string 24 | Committer_email string 25 | Committer_name string 26 | Compare_url string 27 | Duration int 28 | Finished_at string 29 | Id int 30 | Message string 31 | Number string 32 | Repository TRRepository 33 | Started_at string 34 | State string 35 | Status int 36 | Status_message string 37 | Type string 38 | Pull_request bool 39 | Pull_request_number string 40 | Pull_request_type string 41 | Tag string 42 | } 43 | 44 | // Return travis data. 45 | func getTravisData(decoder *json.Decoder) (string, string) { 46 | var tEvent TRPayload 47 | decoder.Decode(&tEvent) 48 | if tEvent.Id > 0 { 49 | event := "Travis: " + tEvent.Status_message + " for " + 50 | tEvent.Repository.Name 51 | desc := "Status: " + tEvent.Status_message + 52 | "\n Duration: " + strconv.Itoa(tEvent.Duration) + 53 | "\n Message: " + tEvent.Message + 54 | "\n Build Number: " + tEvent.Number + 55 | "\n Type: " + tEvent.Type + 56 | "\n Compare URL: " + tEvent.Compare_url + 57 | "\n Committer Name: " + tEvent.Committer_name + 58 | "\n Build Url: " + tEvent.Build_url 59 | return event, desc 60 | } 61 | return "", "" 62 | } 63 | -------------------------------------------------------------------------------- /services/trello.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "google.golang.org/appengine/log" 8 | "google.golang.org/appengine/urlfetch" 9 | "io/ioutil" 10 | "strings" 11 | ) 12 | 13 | const trelloApiURL = "https://api.trello.com/1/" 14 | 15 | // To send as trello api payload. 16 | type TrelloPayLoad struct { 17 | Name string 18 | Desc string 19 | } 20 | 21 | // Push to Trello 22 | func PushToTrello( 23 | context context.Context, listId string, accessToken string, 24 | event string, desc string) { 25 | url := "https://api.trello.com/1/lists/" + listId + 26 | "/cards?key=" + trelloKey + "&token=" + accessToken 27 | payload := &TrelloPayLoad{ 28 | Name: event, 29 | Desc: string(desc), 30 | } 31 | str, _ := json.Marshal(payload) 32 | jsonStr := strings.Replace(string(str), "Name", "name", 1) 33 | jsonStr = strings.Replace(jsonStr, "Desc", "desc", 1) 34 | client := urlfetch.Client(context) 35 | resp, err := client.Post( 36 | url, "application/json", bytes.NewBuffer([]byte(jsonStr))) 37 | if err != nil { 38 | log.Infof(context, "PushToTrello client.Post: %v", err.Error()) 39 | return 40 | } 41 | defer resp.Body.Close() 42 | log.Infof(context, "response Headers:", resp.Header) 43 | body, _ := ioutil.ReadAll(resp.Body) 44 | log.Infof(context, "response Body:", string(body)) 45 | } 46 | 47 | // Return Trello Key 48 | func GetAuthorizeUrl() string { 49 | return "https://trello.com/1/OAuthAuthorizeToken" + 50 | "?key=" + trelloKey + "&callback_method=fragment&scope=read,write" + 51 | "&name=PGWebhook&scope=read,write&expiration=never" + 52 | "&return_url=http://webhook.co/redirect" 53 | } 54 | 55 | // Get list of borads 56 | func GetBoards(context context.Context, accessToken string) string { 57 | url := trelloApiURL + "members/me/boards?fields=name&key=" + trelloKey + "&token=" + 58 | accessToken 59 | return getResponse(context, url) 60 | } 61 | 62 | // Get list of borads 63 | func GetBoardLists( 64 | context context.Context, boardId string, accessToken string) string { 65 | url := trelloApiURL + "boards/" + boardId + "/lists?fields=name&key=" + 66 | trelloKey + "&token=" + accessToken 67 | return getResponse(context, url) 68 | } 69 | -------------------------------------------------------------------------------- /services/utils.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "google.golang.org/appengine" 7 | "google.golang.org/appengine/log" 8 | "google.golang.org/appengine/urlfetch" 9 | "io/ioutil" 10 | "math/rand" 11 | "net/http" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 17 | const ( 18 | letterIdxBits = 6 // 6 bits to represent a letter index 19 | letterIdxMask = 1< -1 { 82 | return sd_event, sd_desc 83 | } 84 | return "", "" 85 | } 86 | 87 | // Return type of hook. 88 | func getHookType(request *http.Request) string { 89 | context := appengine.NewContext(request) 90 | log.Infof(context, "%s", request.Header) 91 | log.Infof(context, "%s", request.Body) 92 | if request.Header.Get("X-Github-Event") != "" { 93 | return "github" 94 | } else if request.Header.Get("X-Sender") == "Doorbell" { 95 | return "doorbell" 96 | } else if strings.Index(request.Header.Get("User-Agent"), "Bitbucket") > -1 { 97 | return "bitbucket" 98 | } else if request.Header.Get("Travis-Repo-Slug") != "" { 99 | return "travis" 100 | } else if strings.Index(request.Header.Get("User-Agent"), "Jakarta") > -1 { 101 | return "teamcity" 102 | } else if request.FormValue("message") != "" { 103 | return "pingdom" 104 | } else if strings.Index(request.Header.Get("User-Agent"), "Java/1.8") > -1 { 105 | return "jenkins" 106 | } else if strings.Index(request.Header.Get("User-Agent"), "Faraday") > -1 { 107 | return "fabric" 108 | } else if strings.Index(request.Header.Get("User-Agent"), "Grafana") > -1 { 109 | return "grafana" 110 | } else if request.Header.Get("x-adsk-delivery-id") != "" { 111 | return "ad" 112 | } else if strings.Index(request.Header.Get("User-Agent"), "Custom1") > -1 || 113 | request.Header.Get("X-Newrelic-Id") != "" { 114 | return "custom1" 115 | } 116 | return "" 117 | } 118 | 119 | // Return random alphanumeric string 120 | func GetAlphaNumberic(n int) string { 121 | var src = rand.NewSource(time.Now().UnixNano()) 122 | b := make([]byte, n) 123 | // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! 124 | for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { 125 | if remain == 0 { 126 | cache, remain = src.Int63(), letterIdxMax 127 | } 128 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 129 | b[i] = letterBytes[idx] 130 | i-- 131 | } 132 | cache >>= letterIdxBits 133 | remain-- 134 | } 135 | return string(b) 136 | } 137 | -------------------------------------------------------------------------------- /static/css/home.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #F7F8FA; 3 | } 4 | a { 5 | color: #3f51b5; 6 | text-decoration: none; 7 | } 8 | paper-toolbar a { 9 | color: white; 10 | } 11 | paper-card { 12 | margin: 20px; 13 | } 14 | .card-actions { 15 | padding: 15px !important; 16 | text-align: right; 17 | } 18 | paper-material.paper-material-0[elevation="1"] { 19 | box-shadow: 1px 0px 3px 0px rgba(0, 0, 0, 0.14), 0 1px 5px 0 rgba(0, 0, 0, 0.12), 0 2px 1px -2px rgba(0, 0, 0, 0.2); 20 | border-radius: 4px; 21 | } 22 | paper-material .header { 23 | border-bottom: 2px solid #B6BCE0; 24 | } 25 | .paper-card-0 .header.paper-card .title-text.paper-card { 26 | color: #666; 27 | } 28 | paper-card.paper-card-0 .card-content { 29 | color: #555; 30 | } 31 | .content-icon { 32 | margin-right: 20px; 33 | } 34 | .list .avatar { 35 | background-size: 100%; 36 | height: 56px; 37 | width: 56px; 38 | } 39 | .list .github { 40 | background-image: url("https://storage.googleapis.com/pgwebhook/octocat.png"); 41 | } 42 | .list .bitbucket { 43 | background-image: url("https://storage.googleapis.com/pgwebhook/bitbucket.png"); 44 | } 45 | .list .doorbell { 46 | background-image: url("https://storage.googleapis.com/pgwebhook/doorbell.png"); 47 | } 48 | .list .travis { 49 | background-image: url("https://storage.googleapis.com/pgwebhook/travis.png"); 50 | } 51 | .list .trello { 52 | background-image: url("https://storage.googleapis.com/pgwebhook/trello.png"); 53 | } 54 | .list .telegram { 55 | background-image: url("https://storage.googleapis.com/pgwebhook/telegram.png"); 56 | } 57 | .list .pushover { 58 | background-image: url("https://storage.googleapis.com/pgwebhook/pushover.png"); 59 | } 60 | .list .hipchat { 61 | background-image: url("https://storage.googleapis.com/pgwebhook/hipchat.png"); 62 | } 63 | .list .teamcity { 64 | background-image: url("https://storage.googleapis.com/pgwebhook/teamcity.png"); 65 | } 66 | .list .pingdom { 67 | background-image: url("https://storage.googleapis.com/pgwebhook/pingdom.png"); 68 | } 69 | .list .jenkins { 70 | background-image: url("https://www.cloudbees.com/sites/default/files/headshot.png"); 71 | } 72 | .logo { 73 | background-image: url("https://storage.googleapis.com/pgwebhook/Webhook.png"); 74 | background-position-y: 30px; 75 | background-repeat: no-repeat; 76 | background-size: 100%; 77 | font-size: 20px; 78 | font-weight: bold; 79 | height: 130px; 80 | margin: 20px auto; 81 | text-align: center; 82 | width: 100px; 83 | } 84 | #drawer.paper-drawer-panel > [drawer] { 85 | background-color: #F7F8FA; 86 | box-shadow: -2px 1px 5px 0 rgba(138, 138, 138, 0.18) inset; 87 | } 88 | paper-menu paper-item { 89 | cursor: pointer; 90 | position: relative; 91 | } 92 | paper-menu { 93 | background-color: rgba(0, 0, 0, 0) !important; 94 | } 95 | .infograph { 96 | max-width: 706px; 97 | width: 100%; 98 | } 99 | -------------------------------------------------------------------------------- /static/css/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #eee; 3 | } 4 | paper-toolbar a { 5 | color: #FFF; 6 | text-decoration: none; 7 | } 8 | paper-button.colorful { 9 | color: #4285f4; 10 | } 11 | paper-button[raised].colorful { 12 | background: #4285f4; 13 | color: #fff; 14 | } 15 | paper-button[toggles][active][raised].colorful { 16 | background-color: rgba(66, 133, 244, 0.75); 17 | } 18 | paper-dialog { 19 | position: fixed; 20 | top: 50px; 21 | } 22 | wh-created iron-list { 23 | padding-bottom: 16px; 24 | } 25 | wh-created .item { 26 | background-color: white; 27 | border-radius: 2px; 28 | border: 1px solid #ddd; 29 | box-shadow: 0 1px 2px 0 rgba(0,0,0,.16); 30 | margin: 20px 20px 0 20px; 31 | padding: 20px; 32 | } 33 | wh-created .item span:not(.nostyle) { 34 | font-size: 20px; 35 | margin-right: 5px; 36 | margin-top: 8px; 37 | } 38 | paper-dialog .pad { 39 | padding: 20px; 40 | } 41 | .base { 42 | height: 100vh; 43 | } 44 | .header > .content { 45 | background: #eee; 46 | } 47 | .pad { 48 | padding: 0 16px; 49 | } 50 | .hide a { 51 | color: #2481CC; 52 | text-decoration: none; 53 | } 54 | .apps { 55 | margin: 20px 0; 56 | } 57 | .apps .app { 58 | display: inline; 59 | margin: 5px; 60 | } 61 | .app img { 62 | -webkit-filter: grayscale(100%); 63 | box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.16); 64 | cursor: pointer; 65 | filter: grayscale(100%); 66 | padding: 5px; 67 | } 68 | .app img:hover, .app img.selected { 69 | -webkit-filter: grayscale(0%); 70 | filter: grayscale(0%); 71 | } 72 | .hide { 73 | display: none; 74 | } 75 | .conf h2.margin { 76 | margin-bottom: 15px; 77 | } 78 | .fields div { 79 | margin: 15px; 80 | } 81 | .fields div select { 82 | font-size: 20px; 83 | margin: 0 10px; 84 | } 85 | .error { 86 | color: #f44336; 87 | float: left; 88 | } 89 | -------------------------------------------------------------------------------- /static/js/common.js: -------------------------------------------------------------------------------- 1 | /*jslint browser: true */ 2 | /*global $,document,window*/ 3 | 4 | (function () { 5 | "use strict"; 6 | 7 | var wh = { 8 | util: { 9 | clip: function (text) { 10 | var copyElement = document.createElement("input"); 11 | copyElement.setAttribute("type", "text"); 12 | copyElement.setAttribute("value", text); 13 | copyElement = document.body.appendChild(copyElement); 14 | copyElement.select(); 15 | try { 16 | document.execCommand("copy"); 17 | } finally { 18 | copyElement.remove(); 19 | } 20 | } 21 | }, 22 | ajax: { 23 | handler: { 24 | board: function (data) { 25 | $("#boards").html(""); 26 | $.each(data, function (index) { 27 | var temp = data[index]; 28 | if (index === 0) { 29 | $("#boardName").val(temp.name); 30 | } 31 | $("#boards").append( 32 | ""); 33 | }); 34 | }, 35 | list: function (data) { 36 | $("#lists").html(""); 37 | $.each(data, function (index) { 38 | var temp = data[index]; 39 | if (index === 0) { 40 | $("#listName").val(temp.name); 41 | } 42 | $("#lists").append( 43 | ""); 44 | }); 45 | }, 46 | hookResponse: function (data) { 47 | if (!data.success) { 48 | $(".error").text(data.reason); 49 | } else { 50 | $("#toast").attr("text", "Webhook Created. webhook.co/" + data.handler); 51 | $("#toast")[0].show(); 52 | $("#modal")[0].toggle(); 53 | window.setTimeout(function () { 54 | $("iron-ajax").attr("url", "/created.json?" + data.handler); 55 | }, 2000); 56 | } 57 | } 58 | }, 59 | request: { 60 | board: function () { 61 | $.ajax({ 62 | method: "POST", 63 | url: "/trello/boards/list" 64 | }).done(wh.ajax.handler.board); 65 | }, 66 | list: function (value) { 67 | $.ajax({ 68 | method: "POST", 69 | url: "/trello/lists/" + value 70 | }).done(wh.ajax.handler.list); 71 | }, 72 | createHook: function (data) { 73 | $.ajax({ 74 | url: "/save", 75 | method: "POST", 76 | data: data 77 | }).done(wh.ajax.handler.hookResponse); 78 | } 79 | } 80 | }, 81 | event: { 82 | handler: { 83 | boards: function () { 84 | $("#boardName").val($("option:selected", this).text()); 85 | $(".list").show(); 86 | var value = $(this).val(); 87 | if (!value.length || value === "0") { 88 | $("#lists").html(""); 89 | $(".list").hide(); 90 | } 91 | wh.ajax.request.list(value); 92 | }, 93 | lists: function () { 94 | $("#listName").val($("option:selected", this).text()); 95 | }, 96 | openCreateDialog: function () { 97 | $("#modal")[0].toggle(); 98 | }, 99 | showForm: function () { 100 | var service = $(this).attr("alt"); 101 | if (service === "trello" && !$("#boards").text().trim().length) { 102 | wh.ajax.request.board(); 103 | } 104 | $(".hide").hide(); 105 | $("img.selected").removeClass("selected"); 106 | $("." + service).show(); 107 | $(".buttons .hide").show(); 108 | if (!$(".buttons").hasClass("pad")) { 109 | $(".buttons").addClass("pad"); 110 | } 111 | $(".error").text(""); 112 | $(this).addClass("selected"); 113 | }, 114 | createHook: function () { 115 | var service = $("img.selected").attr("alt"); 116 | var data = { 117 | "service": service 118 | }; 119 | if (service === "trello") { 120 | data.boardName = $("#boardName").val(); 121 | data.boardId = $("#boards").val(); 122 | data.listName = $("#listName").val(); 123 | data.listId = $("#lists").val(); 124 | if (!data.boardName.length || !data.boardId.length || !data.listId.length || !data.listName.length) { 125 | $(".error").text("Provide all information."); 126 | return; 127 | } 128 | } else if (service === "telegram") { 129 | data.teleCode = $("#teleCode").val(); 130 | if (data.teleCode.length !== 6) { 131 | $(".error").text("Invalid code."); 132 | return; 133 | } 134 | } else if (service === "slack") { 135 | data.slack_url = $("#slack").val(); 136 | data.slack_channel = $("#slack_channel").val(); 137 | if (data.slack_url.search("https://hooks.slack.com/services/") === -1) { 138 | $(".error").text("Invalid Slack URL."); 139 | return; 140 | } 141 | if (data.slack_channel[0] !== '@' && data.slack_channel[0] !== '#') { 142 | $(".error").text("Invalid Channel / Username."); 143 | return; 144 | } 145 | } else if (service === "pushover") { 146 | data.poUserkey = $("#poUserkey").val(); 147 | if (data.poUserkey.length < 24) { 148 | $(".error").text("Invalid key."); 149 | } 150 | } else if (service === "hipchat") { 151 | data.hcToken = $("#hcToken").val(); 152 | data.hcRoomid = $("#hcRoomid").val(); 153 | } 154 | wh.ajax.request.createHook(data); 155 | } 156 | }, 157 | add: function () { 158 | var handler = wh.event.handler; 159 | $("#lists").on("change", handler.lists); 160 | $("#boards").on("change", handler.boards); 161 | $("#addHook").on("click", handler.openCreateDialog); 162 | $(".app img").on("click", handler.showForm); 163 | $("#create").on("click", handler.createHook); 164 | } 165 | } 166 | }; 167 | 168 | $(document).ready(function () { 169 | wh.event.add(); 170 | }); 171 | }()); 172 | -------------------------------------------------------------------------------- /structs.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // Response 4 | type Response struct { 5 | Success bool `json:"success"` 6 | Reason string `json:"reason"` 7 | Handler string `json:"handler"` 8 | } 9 | -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Webhook.co - Home 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 |
30 | 31 | 32 | 33 |
webhook.co
34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
44 |
45 | 46 | 47 |
48 |

Select service to connect

49 |
50 |
51 | trello 52 |
53 |
54 | telegram 55 |
56 |
57 | pushover 58 |
59 |
60 | hipchat 61 |
62 |
63 | slack 64 |
65 |
66 |
67 |
68 | {{with .AccessToken}} 69 |

Trello

70 |
71 | Board: 72 | 73 |
74 |
75 | List: 76 | 77 |
78 | {{else}} 79 |

80 | Connect to Trello 81 |

82 | {{end}} 83 |
84 |
85 |

Telegram

86 | 87 |

88 | Add @WebhookCo in Telegram, and generated code using /getcode command 89 |

90 |
91 |
92 |

Slack

93 | 94 | 95 |
96 |
97 |

Pushover

98 | 99 |

100 | Copy your user key from Pushover and paste it here. 101 |

102 |
103 |
104 |

Hipchat

105 | 106 | 107 |

Go to Rooms > Integrations > Build Your Own!, Create your bot and share bot token & room Id here. 108 |

109 |
110 |
111 |
112 |
113 |
114 | Cancel 115 | Create Hook 116 |
117 |
118 |
119 |
120 | 121 |
122 | 123 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /templates/callback.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Webhook - Start 5 | 6 | 7 | Redirecting... 8 |
9 | 10 | 11 |
12 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/components/wh-created.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 57 | 99 | 100 | 141 | -------------------------------------------------------------------------------- /templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Webhook.co 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | 34 | 50 | 51 | 52 |
53 |
54 | 55 | 56 | 57 |
Webhook.co
58 |
59 | 60 |
61 |

Webhook is a method of altering and augmenting the behavior of web applications, or a web pages, using some custom callbacks. These callbacks handled, modified, managed and maintained by some third-party developers and users who may not necessarily be affiliated with the origination web application or web page.

62 |

Webhook.co offers the similar service for your web based application. It simply generates a URL which can be used in all your development application. Once your application performs any action such as git push, Travis pass or Travis fail etc. then Webhook.co will push the same information to your selected services. Isn’t is simple and amazing!

63 |

The popular applications which are already using web hooks include Assembla, CallMyApp, FreshBooks, Google Code, GitHub, Femtoo and PayPal etc. If you are also looking for a solution for your web hooks work then this is the right place to visit.

64 |
65 |

Webhook.co

66 |

67 | Webhook.co provides a specific URL to users for various events, a web application will POST the data to those URLs when an action perform. It is very simple to use and it’s up to you and whatever you want to accomplish. Using our service, developers can create notification for themselves, real-time synchronization with other app, validate the data and also prevent it from being used by the application and process the data and repost using various APIs. 68 |

69 |

70 | 71 |

72 |

Webhook.co provide hooks for most popular platforms such as Github (only push action), Bitbucket, Travis, Doorbell.io, TeamCity and Pingdom. We also offer services for Telegram, Pushover, Hipchat and Trello. The service includes three types of actions: 73 |

    74 |
  • Push: is used to receive data in real time.
  • 75 |
  • Plugins: is used to process data and return something.
  • 76 |
77 |

78 |

Webhook.co team let you offer a URL which is used to access all the APIs. The same URL works for accessing all the hooks and services offered by us. Our team is dedicated to offering these services as easy as possible to our client, users, and developers. We also listen to our end-users for any modification, features, queries or issues in order to make our service more flexible, durable and easy to use.

79 |
80 |
81 | 82 |
83 |
84 | 85 |
86 | 87 |
Github
88 |
Only Push
89 |
90 |
91 | 92 |
93 | 94 |
Bitbucket
95 |
Complete
96 |
97 | 98 |
99 | 100 |
101 | 102 |
Doorbell.io
103 |
Complete
104 |
105 | 106 |
107 | 108 |
109 | 110 |
Travis
111 |
Complete
112 |
113 | 114 |
115 | 116 |
117 | 118 |
TeamCity
119 |
Complete
120 |
121 | 122 |
123 | 124 |
125 | 126 |
Pingdom
127 |
Complete
128 |
129 | 130 |
131 | 132 |
133 | 134 |
Jenkins
135 |
Job Notifications
136 |
137 | 138 |
139 |
140 |
141 |
142 | 143 |
144 |
145 | 146 |
147 | 148 |
Trello
149 |
Create card
150 |
151 |
152 | 153 |
154 | 155 |
Telegram
156 |
@WebhookCoBot
157 |
158 |
159 | 160 |
161 | 162 |
Pushover
163 |
Notification
164 |
165 |
166 | 167 |
168 | 169 |
Hipchat
170 |
Notification
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 | 198 | 199 | 200 | -------------------------------------------------------------------------------- /templates/list.html: -------------------------------------------------------------------------------- 1 | {{define "content"}} 2 | {{range .WH}} 3 |
4 | /hooks/{{.Handler}} 5 |
BoardID: {{.BoardId}}
6 |
ListID: {{.ListId}}
7 |
8 | {{end}} 9 | {{end}} 10 | -------------------------------------------------------------------------------- /utility.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "google.golang.org/appengine/datastore" 6 | ) 7 | 8 | // Return webhook datastore key. 9 | func webhookKey(context context.Context, handler string) *datastore.Key { 10 | return datastore.NewKey(context, "Webhook", handler, 0, nil) 11 | } 12 | 13 | // Return AccessToken datastore key. 14 | func accessTokenKey(context context.Context, email string) *datastore.Key { 15 | return datastore.NewKey(context, "AccessTokens", email, 0, nil) 16 | } 17 | 18 | // Return access token for provided email address. 19 | func getAccessToken(context context.Context, email string) string { 20 | userAccessToken := datastore.NewQuery("AccessTokens").Ancestor( 21 | accessTokenKey(context, email)).Filter("Email =", email).Limit(1) 22 | aTokens := make([]AccessTokens, 0, 1) 23 | userAccessToken.GetAll(context, &aTokens) 24 | if len(aTokens) > 0 { 25 | return aTokens[0].AccessToken 26 | } 27 | return "" 28 | } 29 | 30 | // Return list of webhooks (datastore entities) for given email. 31 | func getWebhooks(context context.Context, email string) []Webhook { 32 | query := datastore.NewQuery("Webhook").Filter("User =", email).Limit(50) 33 | webhooks := make([]Webhook, 0, 50) 34 | query.GetAll(context, &webhooks) 35 | return webhooks 36 | } 37 | 38 | // Return list of webhooks (datastore entities) from given handler. 39 | func getWebhookFromHandler( 40 | context context.Context, handler string) *Webhook { 41 | query := datastore.NewQuery("Webhook").Ancestor( 42 | webhookKey(context, handler)).Limit(1) 43 | webhook := make([]Webhook, 0, 1) 44 | keys, _ := query.GetAll(context, &webhook) 45 | if len(webhook) > 0 { 46 | webhook[0].Count += 1 47 | datastore.Put(context, keys[0], &webhook[0]) 48 | return &webhook[0] 49 | } 50 | return nil 51 | } 52 | 53 | // Delete handler. 54 | func deleteWebhookFromHandler( 55 | context context.Context, handler string) *Webhook { 56 | query := datastore.NewQuery("Webhook").Ancestor( 57 | webhookKey(context, handler)).Limit(1) 58 | webhook := make([]Webhook, 0, 1) 59 | keys, _ := query.GetAll(context, &webhook) 60 | if len(webhook) > 0 { 61 | datastore.Delete(context, keys[0]) 62 | return &webhook[0] 63 | } 64 | return nil 65 | } 66 | --------------------------------------------------------------------------------