Skip to content

Commit 143caea

Browse files
committed
adds functionality to upload snapshot to slack channel
1 parent ca0bb3a commit 143caea

File tree

4 files changed

+52
-34
lines changed

4 files changed

+52
-34
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,25 @@ One is polling - the client calls the device repeatedly and asks if any new even
1010
is a subscription model where the client leaves a callback url and the camera uses this url to post notifications when a new
1111
event is created.
1212

13-
Polling is the most common feature implemented and it is the one that is utilized here. The cameras I have used in the making
14-
of this script dont support subscription.
13+
Polling is the most common feature implemented, and it is the one that is utilized here. The cameras I have used in the making
14+
of this script don't support subscription.
1515

16-
In order to use the polling script you must provide the mandatory arguments or the script will fail immiediately. These include the
16+
In order to use the polling script you must provide the mandatory arguments or the script will fail immediately. These include the
1717
base url of the camera, auth details and a slack webhook to which the notification will be posted.
1818

19+
It is also possible to provide the cooldown time and give the slack channel and bot token. The script will then
20+
grab a snapshot from the camera and upload it to slack. The bot must have file upload privileges. There are more
21+
details in this guide [here](https://api.slack.com/methods/files.upload).
22+
1923
The script was written in Go and therefore must be compiled into a native executable before use like so:
2024

2125
go build motion-poll.go
2226

2327
After that we can use the compiled native executable. Example:
2428

25-
./motion-poll http://my-camera-url:1234 admin nimda garden https://hooks.slack.com/my-fake-webook
29+
./motion-poll my-camera-url:1234 admin nimda garden https://hooks.slack.com/my-fake-webook 30 xoxb-slack-bot-token slack-channel-id
2630

27-
With this command the script will keep running continuosly and polling the camera. After it finds a motion event,
31+
With this command the script will keep running continuously and polling the camera. After it finds a motion event,
2832
it will post to the slack url specified and identify the camera as "garden".
2933

3034
## set-time
@@ -33,7 +37,7 @@ The second script deals with another problem when opting to use these cameras wi
3337
If cut off from access to the cloud, the cameras' system clock quickly falls out of sync with the world clock and is unable to
3438
contact an NTP server in order to correct itself. This script will continue updating the system clock with the local time of
3539
the server in which the script is running every 30 minutes. Even with the low-cost nature of the clocks in these cameras, it is
36-
enough to maintaing a sufficiently low drift for most surveillence purposes.
40+
enough to maintain a sufficiently low drift for most surveillance purposes.
3741

3842
It requires only three arguments for usage - the url and port (seperated by semicolon), the username and the password.
3943
It must also be compiled beforehand.
@@ -43,7 +47,7 @@ It must also be compiled beforehand.
4347

4448
Example:
4549

46-
./set-time http://my-camera-url:1234 admin nimda
50+
./set-time my-camera-url:1234 admin nimda
4751

4852
As with the previous script, this one keeps running and will post the current local time of the server to the camera, thereby synchronizing
4953
its system clock with the servers.

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
github.com/beevik/etree v1.1.0 // indirect
1212
github.com/elgs/gostrgen v0.0.0-20161222160715-9d61ae07eeae // indirect
1313
github.com/gofrs/uuid v3.2.0+incompatible // indirect
14+
github.com/gorilla/websocket v1.5.0 // indirect
15+
github.com/slack-go/slack v0.11.0 // indirect
1416
golang.org/x/net v0.0.0-20200425230154-ff2c4b7c35a0 // indirect
1517
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd // indirect
1618
)

go.sum

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,17 +10,25 @@ github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvSc
1010
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
1111
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
1212
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
13+
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
1314
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
1415
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
1516
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
17+
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
1618
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
19+
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
20+
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
21+
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
1722
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
1823
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
1924
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
2025
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
2126
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
2227
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
28+
github.com/slack-go/slack v0.11.0 h1:sBBjQz8LY++6eeWhGJNZpRm5jvLRNnWBFZ/cAq58a6k=
29+
github.com/slack-go/slack v0.11.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
2330
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
31+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
2432
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
2533
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
2634
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
@@ -37,6 +45,7 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
3745
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
3846
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
3947
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
48+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
4049
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
4150
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
4251
gopkg.in/xmlpath.v2 v2.0.0-20150820204837-860cbeca3ebc h1:LMEBgNcZUqXaP7evD1PZcL6EcDVa2QOFuI+cqM3+AJM=

motion-poll.go

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package main
22

33
import (
4-
"bytes"
54
"fmt"
5+
"github.com/slack-go/slack"
66
"github.com/use-go/onvif"
77
"github.com/use-go/onvif/event"
88
"gopkg.in/xmlpath.v2"
9-
"io"
109
"io/ioutil"
1110
"net/http"
1211
"os"
@@ -23,9 +22,10 @@ import (
2322
2 - password
2423
3 - camera name (or location)
2524
4 - slack hook url
26-
5 - snapshot save path
27-
6 - (optional) cooldown time after motion event detected
28-
7 - (optional) json message template for sprintf ex. ./motion-poll "${<file.json}"
25+
5 - (optional) cooldown time after motion event detected
26+
6 - (optional) slack bot token
27+
7 - (optional) slack channel id
28+
8 - (optional) json message template for sprintf ex. ./motion-poll "${<file.json}"
2929
*/
3030

3131
const ssErrorTemplate = "Error while getting snapshot %s\n"
@@ -63,23 +63,23 @@ func main() {
6363
return
6464
}
6565

66-
// get slack message from template - use default if cli arg is not given
66+
// get Slack message from template - use default if cli arg is not given
6767
camName := args[3]
68-
var msgT string
68+
var msgT, botToken, channelID string
6969
if len(args) > 7 {
70-
msgT = args[7]
70+
botToken = args[6]
71+
channelID = args[7]
72+
}
73+
if len(args) > 8 {
74+
msgT = args[8]
7175
} else {
72-
msgT = `
73-
{
74-
"text" : "Motion detected at %s"
75-
}
76-
`
76+
msgT = "Motion detected at %s"
7777
}
7878

7979
//get cooldown time from args, default 10 seconds
8080
cooldown := 10
81-
if len(args) > 6 {
82-
convInt, err := strconv.Atoi(args[6])
81+
if len(args) > 5 {
82+
convInt, err := strconv.Atoi(args[5])
8383
if err == nil {
8484
cooldown = convInt
8585
} else {
@@ -98,46 +98,49 @@ func main() {
9898
}
9999
}
100100
fmt.Printf("Snapshot url is %s\n", ssUrl)
101+
slackClient := slack.New(botToken)
101102

102-
// continue polling for motion events. if motion is detected, send slack notification
103+
// continue polling for motion events. if motion is detected, send Slack notification
103104
for true {
104105
r2, _ := cam.CallMethod(event.PullMessages{})
105106
bodyBytes, _ := ioutil.ReadAll(r2.Body)
106107
bodyS := string(bodyBytes)
107108
if strings.Contains(bodyS, "<tt:SimpleItem Name=\"IsMotion\" Value=\"true\" />") {
108109
msg := fmt.Sprintf(msgT, camName)
109-
_, err = http.Post(args[4], "aplication/json", bytes.NewReader([]byte(msg)))
110+
err = slack.PostWebhook(args[4], &slack.WebhookMessage{Text: msg})
110111
if err != nil {
111112
fmt.Printf("there was an error while posting the slack notification %s", err)
112113
}
113-
if ssUrl != "" {
114-
getSnapshot(ssUrl, args[5])
114+
if ssUrl != "" && botToken != "" && channelID != "" {
115+
getSnapshot(ssUrl, channelID, *slackClient)
115116
}
116-
117117
time.Sleep(time.Duration(cooldown) * time.Second)
118118
}
119119
time.Sleep(1 * time.Second)
120120
}
121121

122122
}
123123

124-
func getSnapshot(url, path string) {
124+
func getSnapshot(url, channelID string, slackClient slack.Client) {
125125
r, e := http.Get(url)
126126
if e != nil {
127127
fmt.Printf(ssErrorTemplate, e)
128128
return
129129
}
130130
defer r.Body.Close()
131-
132-
file, e := os.Create(fmt.Sprintf("%s/%s.jpeg", path, time.Now().Format("20060102150405")))
133131
if e != nil {
134132
fmt.Printf(ssErrorTemplate, e)
135133
return
136134
}
137-
defer file.Close()
138135

139-
_, e = io.Copy(file, r.Body)
140-
if e != nil {
141-
fmt.Printf(ssErrorTemplate, e)
136+
_, err := slackClient.UploadFile(slack.FileUploadParameters{
137+
Reader: r.Body,
138+
Filetype: "image/png",
139+
Filename: fmt.Sprintf("%s.png", time.Now().Format("20060102150405")),
140+
Channels: []string{channelID},
141+
})
142+
143+
if err != nil {
144+
fmt.Printf("error while posting snapshot %s", err)
142145
}
143146
}

0 commit comments

Comments
 (0)