diff options
Diffstat (limited to 'teleirc/matterbridge/bridge/config/config.go')
| -rw-r--r-- | teleirc/matterbridge/bridge/config/config.go | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/teleirc/matterbridge/bridge/config/config.go b/teleirc/matterbridge/bridge/config/config.go new file mode 100644 index 0000000..18c6092 --- /dev/null +++ b/teleirc/matterbridge/bridge/config/config.go @@ -0,0 +1,441 @@ +package config + +import ( + "bytes" + "io/ioutil" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/fsnotify/fsnotify" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" +) + +const ( + EventJoinLeave = "join_leave" + EventTopicChange = "topic_change" + EventFailure = "failure" + EventFileFailureSize = "file_failure_size" + EventAvatarDownload = "avatar_download" + EventRejoinChannels = "rejoin_channels" + EventUserAction = "user_action" + EventMsgDelete = "msg_delete" + EventFileDelete = "file_delete" + EventAPIConnected = "api_connected" + EventUserTyping = "user_typing" + EventGetChannelMembers = "get_channel_members" + EventNoticeIRC = "notice_irc" +) + +const ParentIDNotFound = "msg-parent-not-found" + +type Message struct { + Text string `json:"text"` + Channel string `json:"channel"` + Username string `json:"username"` + UserID string `json:"userid"` // userid on the bridge + Avatar string `json:"avatar"` + Account string `json:"account"` + Event string `json:"event"` + Protocol string `json:"protocol"` + Gateway string `json:"gateway"` + ParentID string `json:"parent_id"` + Timestamp time.Time `json:"timestamp"` + ID string `json:"id"` + Extra map[string][]interface{} +} + +func (m Message) ParentNotFound() bool { + return m.ParentID == ParentIDNotFound +} + +func (m Message) ParentValid() bool { + return m.ParentID != "" && !m.ParentNotFound() +} + +type FileInfo struct { + Name string + Data *[]byte + Comment string + URL string + Size int64 + Avatar bool + SHA string + NativeID string +} + +type ChannelInfo struct { + Name string + Account string + Direction string + ID string + SameChannel map[string]bool + Options ChannelOptions +} + +type ChannelMember struct { + Username string + Nick string + UserID string + ChannelID string + ChannelName string +} + +type ChannelMembers []ChannelMember + +type Protocol struct { + AllowMention []string // discord + AuthCode string // steam + BindAddress string // mattermost, slack // DEPRECATED + Buffer int // api + Charset string // irc + ClientID string // msteams + ColorNicks bool // only irc for now + Debug bool // general + DebugLevel int // only for irc now + DisableWebPagePreview bool // telegram + EditSuffix string // mattermost, slack, discord, telegram, gitter + EditDisable bool // mattermost, slack, discord, telegram, gitter + HTMLDisable bool // matrix + IconURL string // mattermost, slack + IgnoreFailureOnStart bool // general + IgnoreNicks string // all protocols + IgnoreMessages string // all protocols + Jid string // xmpp + JoinDelay string // all protocols + Label string // all protocols + Login string // mattermost, matrix + LogFile string // general + MediaDownloadBlackList []string + MediaDownloadPath string // Basically MediaServerUpload, but instead of uploading it, just write it to a file on the same server. + MediaDownloadSize int // all protocols + MediaServerDownload string + MediaServerUpload string + MediaConvertTgs string // telegram + MediaConvertWebPToPNG bool // telegram + MessageDelay int // IRC, time in millisecond to wait between messages + MessageFormat string // telegram + MessageLength int // IRC, max length of a message allowed + MessageQueue int // IRC, size of message queue for flood control + MessageSplit bool // IRC, split long messages with newlines on MessageLength instead of clipping + Muc string // xmpp + MxID string // matrix + Name string // all protocols + Nick string // all protocols + NickFormatter string // mattermost, slack + NickServNick string // IRC + NickServUsername string // IRC + NickServPassword string // IRC + NicksPerRow int // mattermost, slack + NoHomeServerSuffix bool // matrix + NoSendJoinPart bool // all protocols + NoTLS bool // mattermost, xmpp + Password string // IRC,mattermost,XMPP,matrix + PrefixMessagesWithNick bool // mattemost, slack + PreserveThreading bool // slack + Protocol string // all protocols + QuoteDisable bool // telegram + QuoteFormat string // telegram + QuoteLengthLimit int // telegram + RealName string // IRC + RejoinDelay int // IRC + ReplaceMessages [][]string // all protocols + ReplaceNicks [][]string // all protocols + RemoteNickFormat string // all protocols + RunCommands []string // IRC + Server string // IRC,mattermost,XMPP,discord,matrix + SessionFile string // msteams,whatsapp + ShowJoinPart bool // all protocols + ShowTopicChange bool // slack + ShowUserTyping bool // slack + ShowEmbeds bool // discord + SkipTLSVerify bool // IRC, mattermost + SkipVersionCheck bool // mattermost + StripNick bool // all protocols + StripMarkdown bool // irc + SyncTopic bool // slack + TengoModifyMessage string // general + Team string // mattermost, keybase + TeamID string // msteams + TenantID string // msteams + Token string // gitter, slack, discord, api, matrix + Topic string // zulip + URL string // mattermost, slack // DEPRECATED + UseAPI bool // mattermost, slack + UseLocalAvatar []string // discord + UseSASL bool // IRC + UseTLS bool // IRC + UseDiscriminator bool // discord + UseFirstName bool // telegram + UseUserName bool // discord, matrix, mattermost + UseInsecureURL bool // telegram + UserName string // IRC + VerboseJoinPart bool // IRC + WebhookBindAddress string // mattermost, slack + WebhookURL string // mattermost, slack +} + +type ChannelOptions struct { + Key string // irc, xmpp + WebhookURL string // discord + Topic string // zulip +} + +type Bridge struct { + Account string + Channel string + Options ChannelOptions + SameChannel bool +} + +type Gateway struct { + Name string + Enable bool + In []Bridge + Out []Bridge + InOut []Bridge +} + +type Tengo struct { + InMessage string + Message string + RemoteNickFormat string + OutMessage string +} + +type SameChannelGateway struct { + Name string + Enable bool + Channels []string + Accounts []string +} + +type BridgeValues struct { + API map[string]Protocol + IRC map[string]Protocol + Mattermost map[string]Protocol + Matrix map[string]Protocol + Slack map[string]Protocol + SlackLegacy map[string]Protocol + Steam map[string]Protocol + Gitter map[string]Protocol + XMPP map[string]Protocol + Discord map[string]Protocol + Telegram map[string]Protocol + Rocketchat map[string]Protocol + SSHChat map[string]Protocol + WhatsApp map[string]Protocol // TODO is this struct used? Search for "SlackLegacy" for example didn't return any results + Zulip map[string]Protocol + Keybase map[string]Protocol + Mumble map[string]Protocol + General Protocol + Tengo Tengo + Gateway []Gateway + SameChannelGateway []SameChannelGateway +} + +type Config interface { + Viper() *viper.Viper + BridgeValues() *BridgeValues + IsKeySet(key string) bool + GetBool(key string) (bool, bool) + GetInt(key string) (int, bool) + GetString(key string) (string, bool) + GetStringSlice(key string) ([]string, bool) + GetStringSlice2D(key string) ([][]string, bool) +} + +type config struct { + sync.RWMutex + + logger *logrus.Entry + v *viper.Viper + cv *BridgeValues +} + +// NewConfig instantiates a new configuration based on the specified configuration file path. +func NewConfig(rootLogger *logrus.Logger, cfgfile string) Config { + logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"}) + + viper.SetConfigFile(cfgfile) + input, err := ioutil.ReadFile(cfgfile) + if err != nil { + logger.Fatalf("Failed to read configuration file: %#v", err) + } + + cfgtype := detectConfigType(cfgfile) + mycfg := newConfigFromString(logger, input, cfgtype) + if mycfg.cv.General.LogFile != "" { + logfile, err := os.OpenFile(mycfg.cv.General.LogFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err == nil { + logger.Info("Opening log file ", mycfg.cv.General.LogFile) + rootLogger.Out = logfile + } else { + logger.Warn("Failed to open ", mycfg.cv.General.LogFile) + } + } + if mycfg.cv.General.MediaDownloadSize == 0 { + mycfg.cv.General.MediaDownloadSize = 1000000 + } + viper.WatchConfig() + viper.OnConfigChange(func(e fsnotify.Event) { + logger.Println("Config file changed:", e.Name) + }) + return mycfg +} + +// detectConfigType detects JSON and YAML formats, defaults to TOML. +func detectConfigType(cfgfile string) string { + fileExt := filepath.Ext(cfgfile) + switch fileExt { + case ".json": + return "json" + case ".yaml", ".yml": + return "yaml" + } + return "toml" +} + +// NewConfigFromString instantiates a new configuration based on the specified string. +func NewConfigFromString(rootLogger *logrus.Logger, input []byte) Config { + logger := rootLogger.WithFields(logrus.Fields{"prefix": "config"}) + return newConfigFromString(logger, input, "toml") +} + +func newConfigFromString(logger *logrus.Entry, input []byte, cfgtype string) *config { + viper.SetConfigType(cfgtype) + viper.SetEnvPrefix("matterbridge") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) + viper.AutomaticEnv() + + if err := viper.ReadConfig(bytes.NewBuffer(input)); err != nil { + logger.Fatalf("Failed to parse the configuration: %s", err) + } + + cfg := &BridgeValues{} + if err := viper.Unmarshal(cfg); err != nil { + logger.Fatalf("Failed to load the configuration: %s", err) + } + return &config{ + logger: logger, + v: viper.GetViper(), + cv: cfg, + } +} + +func (c *config) BridgeValues() *BridgeValues { + return c.cv +} + +func (c *config) Viper() *viper.Viper { + return c.v +} + +func (c *config) IsKeySet(key string) bool { + c.RLock() + defer c.RUnlock() + return c.v.IsSet(key) +} + +func (c *config) GetBool(key string) (bool, bool) { + c.RLock() + defer c.RUnlock() + return c.v.GetBool(key), c.v.IsSet(key) +} + +func (c *config) GetInt(key string) (int, bool) { + c.RLock() + defer c.RUnlock() + return c.v.GetInt(key), c.v.IsSet(key) +} + +func (c *config) GetString(key string) (string, bool) { + c.RLock() + defer c.RUnlock() + return c.v.GetString(key), c.v.IsSet(key) +} + +func (c *config) GetStringSlice(key string) ([]string, bool) { + c.RLock() + defer c.RUnlock() + return c.v.GetStringSlice(key), c.v.IsSet(key) +} + +func (c *config) GetStringSlice2D(key string) ([][]string, bool) { + c.RLock() + defer c.RUnlock() + + res, ok := c.v.Get(key).([]interface{}) + if !ok { + return nil, false + } + var result [][]string + for _, entry := range res { + result2 := []string{} + for _, entry2 := range entry.([]interface{}) { + result2 = append(result2, entry2.(string)) + } + result = append(result, result2) + } + return result, true +} + +func GetIconURL(msg *Message, iconURL string) string { + info := strings.Split(msg.Account, ".") + protocol := info[0] + name := info[1] + iconURL = strings.Replace(iconURL, "{NICK}", msg.Username, -1) + iconURL = strings.Replace(iconURL, "{BRIDGE}", name, -1) + iconURL = strings.Replace(iconURL, "{PROTOCOL}", protocol, -1) + return iconURL +} + +type TestConfig struct { + Config + + Overrides map[string]interface{} +} + +func (c *TestConfig) IsKeySet(key string) bool { + _, ok := c.Overrides[key] + return ok || c.Config.IsKeySet(key) +} + +func (c *TestConfig) GetBool(key string) (bool, bool) { + val, ok := c.Overrides[key] + if ok { + return val.(bool), true + } + return c.Config.GetBool(key) +} + +func (c *TestConfig) GetInt(key string) (int, bool) { + if val, ok := c.Overrides[key]; ok { + return val.(int), true + } + return c.Config.GetInt(key) +} + +func (c *TestConfig) GetString(key string) (string, bool) { + if val, ok := c.Overrides[key]; ok { + return val.(string), true + } + return c.Config.GetString(key) +} + +func (c *TestConfig) GetStringSlice(key string) ([]string, bool) { + if val, ok := c.Overrides[key]; ok { + return val.([]string), true + } + return c.Config.GetStringSlice(key) +} + +func (c *TestConfig) GetStringSlice2D(key string) ([][]string, bool) { + if val, ok := c.Overrides[key]; ok { + return val.([][]string), true + } + return c.Config.GetStringSlice2D(key) +} |
