mqttim

MQTT ↔ Instant Messaging Bridge
git clone https://git.instinctive.eu/mqttim.git
Log | Files | Refs | README | LICENSE

main.go (3256B)


      1 package main
      2 
      3 import (
      4 	"bytes"
      5 	"errors"
      6 	"fmt"
      7 	"github.com/go-mqtt/mqtt"
      8 	"github.com/pelletier/go-toml/v2"
      9 	"github.com/thoj/go-ircevent"
     10 	"log"
     11 	"os"
     12 	"strings"
     13 	"time"
     14 )
     15 
     16 type IrcConfig struct {
     17 	Channel    string
     18 	Server     string
     19 	Nick       string
     20 	CmdStart   string
     21 	CmdMid     string
     22 	CmdEnd     string
     23 	MaxLine    int
     24 	ContSuffix string
     25 	ContPrefix string
     26 	Verbose    bool
     27 }
     28 
     29 type MqttConfig struct {
     30 	Server   string
     31 	UserName string
     32 	Password string
     33 }
     34 
     35 type Config struct {
     36 	Irc  IrcConfig
     37 	Mqtt MqttConfig
     38 }
     39 
     40 type Msg struct {
     41 	Topic   []byte
     42 	Message []byte
     43 }
     44 
     45 func readConfig(path string, config *Config) (err error) {
     46 	var f *os.File
     47 	f, err = os.Open("mqttim.toml")
     48 	if err != nil {
     49 		log.Fatal(err)
     50 		return err
     51 	}
     52 	defer f.Close()
     53 
     54 	d := toml.NewDecoder(f)
     55 	err = d.Decode(config)
     56 	if err != nil {
     57 		log.Fatal(err)
     58 	}
     59 
     60 	return err
     61 }
     62 
     63 func main() {
     64 	var err error
     65 	var config Config
     66 	var m *mqtt.Client
     67 
     68 	ircQueue := make(chan Msg)
     69 
     70 	err = readConfig("mqttim.toml", &config)
     71 	if err != nil {
     72 		return
     73 	}
     74 
     75 	m, err = mqtt.VolatileSession("mqttim", &mqtt.Config{
     76 		Dialer:       mqtt.NewDialer("tcp", config.Mqtt.Server),
     77 		PauseTimeout: 4 * time.Second,
     78 		UserName:     config.Mqtt.UserName,
     79 		Password:     []byte(config.Mqtt.Password),
     80 	})
     81 	if err != nil {
     82 		log.Fatal(err)
     83 		return
     84 	}
     85 	go m.Subscribe(nil, "#")
     86 
     87 	i := irc.IRC(config.Irc.Nick, "mqttim")
     88 	if config.Irc.Verbose {
     89 		i.VerboseCallbackHandler = true
     90 		i.Debug = true
     91 	}
     92 	i.AddCallback("001", func(e *irc.Event) { i.Join(config.Irc.Channel) })
     93 	i.AddCallback("366", func(e *irc.Event) {})
     94 	i.AddCallback("PRIVMSG", func(e *irc.Event) {
     95 		msg := e.Message()
     96 		if !strings.HasPrefix(msg, config.Irc.CmdStart) || !strings.HasSuffix(msg, config.Irc.CmdEnd) {
     97 			return
     98 		}
     99 		topic, payload, found := strings.Cut(msg, config.Irc.CmdMid)
    100 		if found {
    101 			go m.Publish(nil, []byte(payload), topic)
    102 		}
    103 	})
    104 	err = i.Connect(config.Irc.Server)
    105 	if err != nil {
    106 		fmt.Printf("Err %s", err)
    107 		return
    108 	}
    109 	go mqtt2irc(m, ircQueue, &config)
    110 	go ircSender(&config.Irc, i, ircQueue)
    111 	i.Loop()
    112 }
    113 
    114 func dup(src []byte) []byte {
    115 	res := make([]byte, len(src))
    116 	copy(res, src)
    117 	return res
    118 }
    119 
    120 func mqtt2irc(m *mqtt.Client, c chan Msg, config *Config) error {
    121 	var big *mqtt.BigMessage
    122 
    123 	for {
    124 		message, topic, err := m.ReadSlices()
    125 		switch {
    126 		case err == nil:
    127 			c <- Msg{Topic: dup(topic), Message: dup(message)}
    128 		case errors.As(err, &big):
    129 			c <- Msg{Topic: dup(topic), Message: []byte("<Big Message>")}
    130 		default:
    131 			log.Print(err)
    132 			return err
    133 		}
    134 	}
    135 }
    136 
    137 func ircSender(config *IrcConfig, i *irc.Connection, c chan Msg) error {
    138 	var buf bytes.Buffer
    139 
    140 	for {
    141 		m := <-c
    142 
    143 		if len(m.Topic)+2+len(m.Message) < config.MaxLine {
    144 			i.Privmsgf(config.Channel, "%s: %s", m.Topic, m.Message)
    145 		} else {
    146 			for s := 0; s < len(m.Message); {
    147 				l := len(m.Message) - s
    148 				buf.Reset()
    149 				buf.Write(m.Topic)
    150 				buf.WriteString(": ")
    151 				if s > 0 {
    152 					buf.WriteString(config.ContPrefix)
    153 				}
    154 				if buf.Len()+l <= config.MaxLine {
    155 					buf.Write(m.Message[s:])
    156 				} else {
    157 					l = config.MaxLine - buf.Len() - len(config.ContSuffix)
    158 					buf.Write(m.Message[s : s+l])
    159 					buf.WriteString(config.ContSuffix)
    160 				}
    161 				i.Privmsg(config.Channel, string(buf.Bytes()))
    162 				s += l
    163 			}
    164 		}
    165 	}
    166 }