-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathplugin.go
221 lines (178 loc) · 5.64 KB
/
plugin.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package main
import (
"encoding/json"
"errors"
"math/rand"
"time"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/gotify/plugin-api"
)
var (
ErrInvalidAddress = errors.New("invalid broker address")
)
// GetGotifyPluginInfo returns gotify plugin info
func GetGotifyPluginInfo() plugin.Info {
return plugin.Info{
Name: "MQTT",
ModulePath: "github.com/tystuyfzand/gotify-mqtt",
Author: "Tyler Stuyfzand",
Website: "https://meow.tf",
}
}
type Server struct {
Address string
Username string
Password string
ClientID string
Subscribe []string
}
type Config struct {
Servers []*Server
}
// Plugin is plugin instance
type Plugin struct {
userCtx plugin.UserContext
msgHandler plugin.MessageHandler
config *Config
clients []mqtt.Client
enabled bool
}
// SetMessageHandler implements plugin.Messenger
// Invoked during initialization
func (p *Plugin) SetMessageHandler(h plugin.MessageHandler) {
p.msgHandler = h
}
// Enable adds users to the context map which maps to a Plugin.
func (p *Plugin) Enable() error {
p.enabled = true
p.connectClients()
return nil
}
// Disable removes users from the context map.
func (p *Plugin) Disable() error {
p.enabled = false
p.disconnectClients()
return nil
}
// DefaultConfig implements plugin.Configurer
// The default configuration will be provided to the user for future editing. Also used for Unmarshaling.
// Invoked whenever an unmarshaling is required.
func (p *Plugin) DefaultConfig() interface{} {
return &Config{
Servers: []*Server{
&Server{Address: "127.0.0.1:1883", Subscribe: []string{"*"}},
},
}
}
// ValidateAndSetConfig will be called every time the plugin is initialized or the configuration has been changed by the user.
// Plugins should validate the configuration and optionally return an error.
// Parameter is guaranteed to be the same type as the return type of DefaultConfig(), so it is safe to do a hard type assertion here.
//
// "Validation" in this context means to check for conflicting or impossible values, such as a non-URL on a field which should only contain a URL.
// In order to make sure that the plugin instance is always running in a valid state, this method should always accept the result of DefaultConfig()
//
// Invoked on initialization to provide initial configuration. Return nil to accept or return error to indicate that the config is obsolete.
// When the configuration is marked obsolete due to an unmarshaling error or rejection on the plugin side, the plugin is disabled automatically and the user is notified to resolve the config confliction.
// Invoked every time the config update API is called. Check the configuration and return nil to accept or return error to indicate that the config is invalid.
// Return a short and consise error here and, if you have detailed suggestions on how to solve the problem, utilize Displayer to provide more information to the user,
func (p *Plugin) ValidateAndSetConfig(c interface{}) error {
config := c.(*Config)
// If listeners are configured, shut them down and start fresh
for _, client := range p.clients {
if client == nil || !client.IsConnected() {
continue
}
go client.Disconnect(500)
}
p.clients = make([]mqtt.Client, len(config.Servers))
for _, server := range config.Servers {
if server.Address == "" {
return ErrInvalidAddress
}
if server.ClientID == "" {
server.ClientID = "gotify-" + randString()
}
}
p.config = config
// If enabled already and config was updated, reconnect clients
if p.enabled {
p.connectClients()
}
return nil
}
func (p *Plugin) disconnectClients() {
if p.clients == nil {
return
}
for _, client := range p.clients {
if client == nil || !client.IsConnected() {
continue
}
go client.Disconnect(500)
}
}
func (p *Plugin) connectClients() error {
p.disconnectClients()
p.clients = make([]mqtt.Client, len(p.config.Servers))
for i, server := range p.config.Servers {
client, err := p.newClient(server)
if err != nil {
return err
}
p.clients[i] = client
}
return nil
}
// handleMessage handles mqtt messages from the client by returning a MessageHandler
// Messages are in either JSON format (same as the Gotify API) or simply a string.
func (p *Plugin) handleMessage(client mqtt.Client, message mqtt.Message) {
payload := message.Payload()
var outgoingMessage plugin.Message
if payload[0] == '{' {
if err := json.Unmarshal(payload, &outgoingMessage); err != nil {
return
}
} else {
outgoingMessage.Message = string(payload)
}
p.msgHandler.SendMessage(outgoingMessage)
}
// newClient creates a new client from the serverConfig
func (p *Plugin) newClient(serverConfig *Server) (mqtt.Client, error) {
opts := mqtt.NewClientOptions()
opts.AddBroker(serverConfig.Address)
opts.SetClientID(serverConfig.ClientID)
if serverConfig.Username != "" {
opts.SetUsername(serverConfig.Username)
}
if serverConfig.Password != "" {
opts.SetPassword(serverConfig.Password)
}
client := mqtt.NewClient(opts)
if token := client.Connect(); token.Wait() && token.Error() != nil {
return nil, token.Error()
}
for _, topic := range serverConfig.Subscribe {
client.Subscribe(topic, 0, p.handleMessage)
}
return client, nil
}
func randString() string {
letterRunes := []rune("0123456789abcdef")
b := make([]rune, 16)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}
// NewGotifyPluginInstance creates a plugin instance for a user context.
func NewGotifyPluginInstance(ctx plugin.UserContext) plugin.Plugin {
rand.Seed(time.Now().UnixNano())
return &Plugin{
userCtx: ctx,
clients: make([]mqtt.Client, 0),
}
}
func main() {
panic("Program must be compiled as a Go plugin")
}