Merge branch 'v0.3.3-notify' into v0.3.3
* v0.3.3-notify: Implement configuration for desktop notifications Add desktop notifications
This commit is contained in:
commit
4b4a0cb5f4
8
Gopkg.lock
generated
8
Gopkg.lock
generated
@ -1,6 +1,12 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/0xAX/notificator"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "88d57ee9043ba88d6a62e437fa15dda1ca0d2b59"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/erroneousboat/termui"
|
name = "github.com/erroneousboat/termui"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -50,6 +56,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "353a2a71e00ecf8dd6123a02828c450fa6d38472a98792d2d8a4cd6349900f11"
|
inputs-digest = "6cdfd0125aad6371a6f4e75c7fc29507cee4a6001a6c68e06c7237066a31153a"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
Slack-Term
|
slack-term
|
||||||
==========
|
==========
|
||||||
|
|
||||||
A [Slack](https://slack.com) client for your terminal.
|
A [Slack](https://slack.com) client for your terminal.
|
||||||
@ -42,7 +42,12 @@ Setup
|
|||||||
"slack_token": "yourslacktokenhere",
|
"slack_token": "yourslacktokenhere",
|
||||||
|
|
||||||
// OPTIONAL: set the width of the sidebar (between 1 and 11), default is 1
|
// OPTIONAL: set the width of the sidebar (between 1 and 11), default is 1
|
||||||
"sidebar_width": 3,
|
"sidebar_width": 1,
|
||||||
|
|
||||||
|
// OPTIONAL: turn on desktop notifications for all incoming messages, set
|
||||||
|
// the value as: "all", and for only mentions and im messages set the
|
||||||
|
// value as: "mention", default is turned off: ""
|
||||||
|
"notify": "",
|
||||||
|
|
||||||
// OPTIONAL: define custom key mappings, defaults are:
|
// OPTIONAL: define custom key mappings, defaults are:
|
||||||
"key_map": {
|
"key_map": {
|
||||||
|
@ -9,9 +9,15 @@ import (
|
|||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
NotifyAll = "all"
|
||||||
|
NotifyMention = "mention"
|
||||||
|
)
|
||||||
|
|
||||||
// Config is the definition of a Config struct
|
// Config is the definition of a Config struct
|
||||||
type Config struct {
|
type Config struct {
|
||||||
SlackToken string `json:"slack_token"`
|
SlackToken string `json:"slack_token"`
|
||||||
|
Notify string `json:"notify"`
|
||||||
SidebarWidth int `json:"sidebar_width"`
|
SidebarWidth int `json:"sidebar_width"`
|
||||||
MainWidth int `json:"-"`
|
MainWidth int `json:"-"`
|
||||||
KeyMap map[string]keyMapping `json:"key_map"`
|
KeyMap map[string]keyMapping `json:"key_map"`
|
||||||
@ -43,6 +49,13 @@ func NewConfig(filepath string) (*Config, error) {
|
|||||||
|
|
||||||
cfg.MainWidth = 12 - cfg.SidebarWidth
|
cfg.MainWidth = 12 - cfg.SidebarWidth
|
||||||
|
|
||||||
|
switch cfg.Notify {
|
||||||
|
case NotifyAll, NotifyMention, "":
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
return &cfg, fmt.Errorf("unsupported setting for notify: %s", cfg.Notify)
|
||||||
|
}
|
||||||
|
|
||||||
termui.ColorMap = map[string]termui.Attribute{
|
termui.ColorMap = map[string]termui.Attribute{
|
||||||
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
"fg": termui.StringToAttribute(cfg.Theme.View.Fg),
|
||||||
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
"bg": termui.StringToAttribute(cfg.Theme.View.Bg),
|
||||||
@ -59,6 +72,7 @@ func getDefaultConfig() Config {
|
|||||||
return Config{
|
return Config{
|
||||||
SidebarWidth: 1,
|
SidebarWidth: 1,
|
||||||
MainWidth: 11,
|
MainWidth: 11,
|
||||||
|
Notify: "",
|
||||||
KeyMap: map[string]keyMapping{
|
KeyMap: map[string]keyMapping{
|
||||||
"command": {
|
"command": {
|
||||||
"i": "mode-insert",
|
"i": "mode-insert",
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
_ "net/http/pprof"
|
_ "net/http/pprof"
|
||||||
|
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
termbox "github.com/nsf/termbox-go"
|
termbox "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
@ -26,6 +27,7 @@ type AppContext struct {
|
|||||||
Config *config.Config
|
Config *config.Config
|
||||||
Debug bool
|
Debug bool
|
||||||
Mode string
|
Mode string
|
||||||
|
Notify *notificator.Notificator
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAppContext creates an application context which can be passed
|
// CreateAppContext creates an application context which can be passed
|
||||||
@ -92,5 +94,6 @@ func CreateAppContext(flgConfig string, flgDebug bool) (*AppContext, error) {
|
|||||||
Config: config,
|
Config: config,
|
||||||
Debug: flgDebug,
|
Debug: flgDebug,
|
||||||
Mode: CommandMode,
|
Mode: CommandMode,
|
||||||
|
Notify: notificator.New(notificator.Options{AppName: "slack-term"}),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
@ -6,15 +6,18 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
"github.com/erroneousboat/termui"
|
"github.com/erroneousboat/termui"
|
||||||
"github.com/nlopes/slack"
|
"github.com/nlopes/slack"
|
||||||
termbox "github.com/nsf/termbox-go"
|
termbox "github.com/nsf/termbox-go"
|
||||||
|
|
||||||
|
"github.com/erroneousboat/slack-term/config"
|
||||||
"github.com/erroneousboat/slack-term/context"
|
"github.com/erroneousboat/slack-term/context"
|
||||||
"github.com/erroneousboat/slack-term/views"
|
"github.com/erroneousboat/slack-term/views"
|
||||||
)
|
)
|
||||||
|
|
||||||
var timer *time.Timer
|
var scrollTimer *time.Timer
|
||||||
|
var notifyTimer *time.Timer
|
||||||
|
|
||||||
// actionMap binds specific action names to the function counterparts,
|
// actionMap binds specific action names to the function counterparts,
|
||||||
// these action names can then be used to bind them to specific keys
|
// these action names can then be used to bind them to specific keys
|
||||||
@ -114,7 +117,7 @@ func messageHandler(ctx *context.AppContext) {
|
|||||||
// Add message to the selected channel
|
// Add message to the selected channel
|
||||||
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
if ev.Channel == ctx.Service.Channels[ctx.View.Channels.SelectedChannel].ID {
|
||||||
|
|
||||||
// reverse order of messages, mainly done
|
// Reverse order of messages, mainly done
|
||||||
// when attachments are added to message
|
// when attachments are added to message
|
||||||
for i := len(msg) - 1; i >= 0; i-- {
|
for i := len(msg) - 1; i >= 0; i-- {
|
||||||
ctx.View.Chat.AddMessage(
|
ctx.View.Chat.AddMessage(
|
||||||
@ -134,7 +137,7 @@ func messageHandler(ctx *context.AppContext) {
|
|||||||
// window (tmux). But only create a notification when
|
// window (tmux). But only create a notification when
|
||||||
// it comes from someone else but the current user.
|
// it comes from someone else but the current user.
|
||||||
if ev.User != ctx.Service.CurrentUserID {
|
if ev.User != ctx.Service.CurrentUserID {
|
||||||
actionNewMessage(ctx, ev.Channel)
|
actionNewMessage(ctx, ev)
|
||||||
}
|
}
|
||||||
case *slack.PresenceChangeEvent:
|
case *slack.PresenceChangeEvent:
|
||||||
actionSetPresence(ctx, ev.User, ev.Presence)
|
actionSetPresence(ctx, ev.User, ev.Presence)
|
||||||
@ -252,12 +255,12 @@ func actionSearch(ctx *context.AppContext, key rune) {
|
|||||||
actionInput(ctx.View, key)
|
actionInput(ctx.View, key)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
timer = time.NewTimer(time.Second / 4)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
<-timer.C
|
<-scrollTimer.C
|
||||||
|
|
||||||
// Only actually search when the time expires
|
// Only actually search when the time expires
|
||||||
term := ctx.View.Input.GetText()
|
term := ctx.View.Input.GetText()
|
||||||
@ -311,15 +314,15 @@ func actionGetMessages(ctx *context.AppContext) {
|
|||||||
// the list without executing the actionChangeChannel event
|
// the list without executing the actionChangeChannel event
|
||||||
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.View.Channels.MoveCursorUp()
|
ctx.View.Channels.MoveCursorUp()
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
timer = time.NewTimer(time.Second / 4)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
<-timer.C
|
<-scrollTimer.C
|
||||||
|
|
||||||
// Only actually change channel when the timer expires
|
// Only actually change channel when the timer expires
|
||||||
actionChangeChannel(ctx)
|
actionChangeChannel(ctx)
|
||||||
@ -331,15 +334,15 @@ func actionMoveCursorUpChannels(ctx *context.AppContext) {
|
|||||||
// the list without executing the actionChangeChannel event
|
// the list without executing the actionChangeChannel event
|
||||||
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
func actionMoveCursorDownChannels(ctx *context.AppContext) {
|
||||||
go func() {
|
go func() {
|
||||||
if timer != nil {
|
if scrollTimer != nil {
|
||||||
timer.Stop()
|
scrollTimer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.View.Channels.MoveCursorDown()
|
ctx.View.Channels.MoveCursorDown()
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
timer = time.NewTimer(time.Second / 4)
|
scrollTimer = time.NewTimer(time.Second / 4)
|
||||||
<-timer.C
|
<-scrollTimer.C
|
||||||
|
|
||||||
// Only actually change channel when the timer expires
|
// Only actually change channel when the timer expires
|
||||||
actionChangeChannel(ctx)
|
actionChangeChannel(ctx)
|
||||||
@ -398,11 +401,24 @@ func actionChangeChannel(ctx *context.AppContext) {
|
|||||||
termui.Render(ctx.View.Chat)
|
termui.Render(ctx.View.Chat)
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionNewMessage(ctx *context.AppContext, channelID string) {
|
// actionNewMessage will set the new message indicator for a channel, and
|
||||||
ctx.Service.MarkAsUnread(channelID)
|
// if configured will also display a desktop notification
|
||||||
|
func actionNewMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||||
|
ctx.Service.MarkAsUnread(ev.Channel)
|
||||||
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
ctx.View.Channels.SetChannels(ctx.Service.ChannelsToString())
|
||||||
termui.Render(ctx.View.Channels)
|
termui.Render(ctx.View.Channels)
|
||||||
|
|
||||||
|
// Terminal bell
|
||||||
fmt.Print("\a")
|
fmt.Print("\a")
|
||||||
|
|
||||||
|
// Desktop notification
|
||||||
|
if ctx.Config.Notify == config.NotifyMention {
|
||||||
|
if ctx.Service.CheckNotifyMention(ev) {
|
||||||
|
createNotifyMessage(ctx, ev)
|
||||||
|
}
|
||||||
|
} else if ctx.Config.Notify == config.NotifyAll {
|
||||||
|
createNotifyMessage(ctx, ev)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
func actionSetPresence(ctx *context.AppContext, channelID string, presence string) {
|
||||||
@ -475,3 +491,21 @@ func getKeyString(e termbox.Event) string {
|
|||||||
ek = pre + mod + k
|
ek = pre + mod + k
|
||||||
return ek
|
return ek
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createNotifyMessage(ctx *context.AppContext, ev *slack.MessageEvent) {
|
||||||
|
go func() {
|
||||||
|
if notifyTimer != nil {
|
||||||
|
notifyTimer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyTimer = time.NewTimer(time.Second * 2)
|
||||||
|
<-notifyTimer.C
|
||||||
|
|
||||||
|
// Only actually notify when time expires
|
||||||
|
ctx.Notify.Push(
|
||||||
|
"slack-term",
|
||||||
|
ctx.Service.CreateNotifyMessage(ev.Channel), "",
|
||||||
|
notificator.UR_NORMAL,
|
||||||
|
)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
@ -306,8 +306,9 @@ func (s *SlackService) MarkAsRead(channelID int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkAsUnread will set the channel as unread
|
// FindChannel will loop over s.Channels to find the index where the
|
||||||
func (s *SlackService) MarkAsUnread(channelID string) {
|
// channelID equals the ID
|
||||||
|
func (s *SlackService) FindChannel(channelID string) int {
|
||||||
var index int
|
var index int
|
||||||
for i, channel := range s.Channels {
|
for i, channel := range s.Channels {
|
||||||
if channel.ID == channelID {
|
if channel.ID == channelID {
|
||||||
@ -315,9 +316,21 @@ func (s *SlackService) MarkAsUnread(channelID string) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkAsUnread will set the channel as unread
|
||||||
|
func (s *SlackService) MarkAsUnread(channelID string) {
|
||||||
|
index := s.FindChannel(channelID)
|
||||||
s.Channels[index].Notification = true
|
s.Channels[index].Notification = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetChannelName will return the name for a specific channelID
|
||||||
|
func (s *SlackService) GetChannelName(channelID string) string {
|
||||||
|
index := s.FindChannel(channelID)
|
||||||
|
return s.Channels[index].Name
|
||||||
|
}
|
||||||
|
|
||||||
// SendMessage will send a message to a particular channel
|
// SendMessage will send a message to a particular channel
|
||||||
func (s *SlackService) SendMessage(channelID int, message string) {
|
func (s *SlackService) SendMessage(channelID int, message string) {
|
||||||
|
|
||||||
@ -519,6 +532,44 @@ func (s *SlackService) CreateMessageFromMessageEvent(message *slack.MessageEvent
|
|||||||
return msgs, nil
|
return msgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckNotifyMention check if the message event is either contains a
|
||||||
|
// mention or is posted on an IM channel
|
||||||
|
func (s *SlackService) CheckNotifyMention(ev *slack.MessageEvent) bool {
|
||||||
|
channel := s.Channels[s.FindChannel(ev.Channel)]
|
||||||
|
switch channel.Type {
|
||||||
|
case ChannelTypeIM:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mentions have the following format:
|
||||||
|
// <@U12345|erroneousboat>
|
||||||
|
// <@U12345>
|
||||||
|
r := regexp.MustCompile(`\<@(\w+\|*\w+)\>`)
|
||||||
|
matches := r.FindAllString(ev.Text, -1)
|
||||||
|
for _, match := range matches {
|
||||||
|
if strings.Contains(match, s.CurrentUserID) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SlackService) CreateNotifyMessage(channelID string) string {
|
||||||
|
channel := s.Channels[s.FindChannel(channelID)]
|
||||||
|
|
||||||
|
switch channel.Type {
|
||||||
|
case ChannelTypeChannel:
|
||||||
|
return fmt.Sprintf("Message received on channel: %s", channel.Name)
|
||||||
|
case ChannelTypeGroup:
|
||||||
|
return fmt.Sprintf("Message received in group: %s", channel.Name)
|
||||||
|
case ChannelTypeIM:
|
||||||
|
return fmt.Sprintf("Message received from: %s", channel.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// parseMessage will parse a message string and find and replace:
|
// parseMessage will parse a message string and find and replace:
|
||||||
// - emoji's
|
// - emoji's
|
||||||
// - mentions
|
// - mentions
|
||||||
|
25
vendor/github.com/0xAX/notificator/.gitignore
generated
vendored
Normal file
25
vendor/github.com/0xAX/notificator/.gitignore
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
|
||||||
|
.idea
|
27
vendor/github.com/0xAX/notificator/LICENSE
generated
vendored
Normal file
27
vendor/github.com/0xAX/notificator/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2014, 0xAX
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
* Neither the name of the {organization} nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
49
vendor/github.com/0xAX/notificator/README.md
generated
vendored
Normal file
49
vendor/github.com/0xAX/notificator/README.md
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
notificator
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Desktop notification with Golang for:
|
||||||
|
|
||||||
|
* Windows with `growlnotify`;
|
||||||
|
* Mac OS X with `terminal-notifier` (if installed) or `osascript` (native, 10.9 Mavericks or Up.);
|
||||||
|
* Linux with `notify-send` for Gnome and `kdialog` for Kde.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
------
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/0xAX/notificator"
|
||||||
|
)
|
||||||
|
|
||||||
|
var notify *notificator.Notificator
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
notify = notificator.New(notificator.Options{
|
||||||
|
DefaultIcon: "icon/default.png",
|
||||||
|
AppName: "My test App",
|
||||||
|
})
|
||||||
|
|
||||||
|
notify.Push("title", "text", "/home/user/icon.png", notificator.UR_CRITICAL)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
TODO
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Add more options for different notificators.
|
||||||
|
|
||||||
|
Сontribution
|
||||||
|
------------
|
||||||
|
|
||||||
|
* Fork;
|
||||||
|
* Make changes;
|
||||||
|
* Send pull request;
|
||||||
|
* Thank you.
|
||||||
|
|
||||||
|
author
|
||||||
|
----------
|
||||||
|
|
||||||
|
[@0xAX](https://twitter.com/0xAX)
|
166
vendor/github.com/0xAX/notificator/notification.go
generated
vendored
Normal file
166
vendor/github.com/0xAX/notificator/notification.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package notificator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Options struct {
|
||||||
|
DefaultIcon string
|
||||||
|
AppName string
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
UR_NORMAL = "normal"
|
||||||
|
UR_CRITICAL = "critical"
|
||||||
|
)
|
||||||
|
|
||||||
|
type notifier interface {
|
||||||
|
push(title string, text string, iconPath string) *exec.Cmd
|
||||||
|
pushCritical(title string, text string, iconPath string) *exec.Cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type Notificator struct {
|
||||||
|
notifier notifier
|
||||||
|
defaultIcon string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Notificator) Push(title string, text string, iconPath string, urgency string) error {
|
||||||
|
icon := n.defaultIcon
|
||||||
|
|
||||||
|
if iconPath != "" {
|
||||||
|
icon = iconPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if urgency == UR_CRITICAL {
|
||||||
|
return n.notifier.pushCritical(title, text, icon).Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
return n.notifier.push(title, text, icon).Run()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type osxNotificator struct {
|
||||||
|
AppName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o osxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
|
||||||
|
// Checks if terminal-notifier exists, and is accessible.
|
||||||
|
|
||||||
|
term_notif := CheckTermNotif()
|
||||||
|
os_version_check := CheckMacOSVersion()
|
||||||
|
|
||||||
|
// if terminal-notifier exists, use it.
|
||||||
|
// else, fall back to osascript. (Mavericks and later.)
|
||||||
|
|
||||||
|
if term_notif == true {
|
||||||
|
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-appIcon", iconPath)
|
||||||
|
} else if os_version_check == true {
|
||||||
|
title = strings.Replace(title, `"`, `\"`, -1)
|
||||||
|
text = strings.Replace(text, `"`, `\"`, -1)
|
||||||
|
|
||||||
|
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
||||||
|
return exec.Command("osascript", "-e", notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
// finally falls back to growlnotify.
|
||||||
|
|
||||||
|
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Causes the notification to stick around until clicked.
|
||||||
|
func (o osxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
|
||||||
|
// same function as above...
|
||||||
|
|
||||||
|
term_notif := CheckTermNotif()
|
||||||
|
os_version_check := CheckMacOSVersion()
|
||||||
|
|
||||||
|
if term_notif == true {
|
||||||
|
// timeout set to 30 seconds, to show the importance of the notification
|
||||||
|
return exec.Command("terminal-notifier", "-title", o.AppName, "-message", text, "-subtitle", title, "-timeout", "30")
|
||||||
|
} else if os_version_check == true {
|
||||||
|
notification := fmt.Sprintf("display notification \"%s\" with title \"%s\" subtitle \"%s\"", text, o.AppName, title)
|
||||||
|
return exec.Command("osascript", "-e", notification)
|
||||||
|
}
|
||||||
|
|
||||||
|
return exec.Command("growlnotify", "-n", o.AppName, "--image", iconPath, "-m", title)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type linuxNotificator struct{}
|
||||||
|
|
||||||
|
func (l linuxNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
return exec.Command("notify-send", "-i", iconPath, title, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Causes the notification to stick around until clicked.
|
||||||
|
func (l linuxNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
return exec.Command("notify-send", "-i", iconPath, title, text, "-u", "critical")
|
||||||
|
}
|
||||||
|
|
||||||
|
type windowsNotificator struct{}
|
||||||
|
|
||||||
|
func (w windowsNotificator) push(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
return exec.Command("growlnotify", "/i:", iconPath, "/t:", title, text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Causes the notification to stick around until clicked.
|
||||||
|
func (w windowsNotificator) pushCritical(title string, text string, iconPath string) *exec.Cmd {
|
||||||
|
return exec.Command("notify-send", "-i", iconPath, title, text, "/s", "true", "/p", "2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(o Options) *Notificator {
|
||||||
|
|
||||||
|
var Notifier notifier
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
|
||||||
|
case "darwin":
|
||||||
|
Notifier = osxNotificator{AppName: o.AppName}
|
||||||
|
case "linux":
|
||||||
|
Notifier = linuxNotificator{}
|
||||||
|
case "windows":
|
||||||
|
Notifier = windowsNotificator{}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Notificator{notifier: Notifier, defaultIcon: o.DefaultIcon}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function for macOS
|
||||||
|
|
||||||
|
func CheckTermNotif() bool {
|
||||||
|
// Checks if terminal-notifier exists, and is accessible.
|
||||||
|
if err := exec.Command("which", "terminal-notifier").Run(); err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// no error, so return true. (terminal-notifier exists)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckMacOSVersion() bool {
|
||||||
|
// Checks if the version of macOS is 10.9 or Higher (osascript support for notifications.)
|
||||||
|
|
||||||
|
cmd := exec.Command("sw_vers", "-productVersion")
|
||||||
|
check, _ := cmd.Output()
|
||||||
|
|
||||||
|
version := strings.Split(strings.TrimSpace(string(check)), ".")
|
||||||
|
|
||||||
|
// semantic versioning of macOS
|
||||||
|
|
||||||
|
major, _ := strconv.Atoi(version[0])
|
||||||
|
minor, _ := strconv.Atoi(version[1])
|
||||||
|
|
||||||
|
if major < 10 {
|
||||||
|
return false
|
||||||
|
} else if major == 10 && minor < 9 {
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user