1072 lines
35 KiB
Go
1072 lines
35 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"database/sql"
|
|
"fmt"
|
|
"html"
|
|
"io"
|
|
"log"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gomarkdown/markdown"
|
|
"gopkg.in/gomail.v2"
|
|
|
|
"github.com/emersion/go-imap"
|
|
"github.com/emersion/go-imap/client"
|
|
_ "github.com/mattn/go-sqlite3"
|
|
"github.com/spf13/viper"
|
|
"maunium.net/go/mautrix"
|
|
)
|
|
|
|
const version = 7
|
|
|
|
var db *sql.DB
|
|
var matrixClient *mautrix.Client
|
|
var tempDir = "temp/"
|
|
var dirPrefix string
|
|
|
|
func initDB() error {
|
|
database, era := sql.Open("sqlite3", dirPrefix+"data.db")
|
|
if era != nil {
|
|
panic(era)
|
|
}
|
|
db = database
|
|
return era
|
|
}
|
|
|
|
//returns true if app should exit
|
|
func initCfg() bool {
|
|
viper.SetConfigType("json")
|
|
viper.SetConfigFile(dirPrefix + "cfg.json")
|
|
viper.AddConfigPath(dirPrefix)
|
|
viper.SetConfigName("cfg")
|
|
|
|
err := viper.ReadInConfig()
|
|
if err != nil {
|
|
fmt.Println("config not found. creating new one")
|
|
|
|
rr := saveVersion(version)
|
|
if rr != nil {
|
|
panic(rr)
|
|
}
|
|
|
|
viper.SetDefault("matrixServer", "matrix.org")
|
|
viper.SetDefault("matrixuserpassword", "AverySecretPassword21!")
|
|
viper.SetDefault("matrixuserid", "@m:matrix.org")
|
|
viper.SetDefault("defaultmailCheckInterval", 30)
|
|
viper.SetDefault("markdownEnabledByDefault", true)
|
|
viper.SetDefault("htmlDefault", false)
|
|
viper.SetDefault("allowed_servers", [1]string{"YourMatrixServerDomain.com"})
|
|
viper.WriteConfigAs(dirPrefix + "cfg.json")
|
|
return true
|
|
}
|
|
|
|
ae := viper.GetInt("defaultmailCheckInterval")
|
|
if ae == 0 {
|
|
viper.SetDefault("defaultmailCheckInterval", 1)
|
|
viper.WriteConfigAs(dirPrefix + "cfg.json")
|
|
}
|
|
|
|
allowedHosts := viper.GetStringSlice("allowed_servers")
|
|
if len(allowedHosts) == 0 {
|
|
allowedHosts = make([]string, 1)
|
|
allowedHosts[0] = "YourMatrixServerDomain.com"
|
|
viper.SetDefault("allowed_servers", allowedHosts)
|
|
viper.WriteConfigAs(dirPrefix + "cfg.json")
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func loginMatrix() {
|
|
fmt.Println("Logging into", viper.GetString("matrixserver"), "as", viper.GetString("matrixuserid"))
|
|
client, err := mautrix.NewClient(viper.GetString("matrixserver"), "", "")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
_, err = client.Login(&mautrix.ReqLogin{
|
|
Type: "m.login.password",
|
|
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: viper.GetString("matrixuserid")},
|
|
Password: viper.GetString("matrixuserpassword"),
|
|
StoreCredentials: true,
|
|
})
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
fmt.Println("Login successful")
|
|
matrixClient = client
|
|
go startMatrixSync(client)
|
|
}
|
|
|
|
func getHostFromMatrixID(matrixID string) (host string, err int) {
|
|
if strings.Contains(matrixID, ":") {
|
|
splt := strings.Split(matrixID, ":")
|
|
if len(splt) == 2 {
|
|
return splt[1], -1
|
|
}
|
|
return "", 1
|
|
}
|
|
return "", 0
|
|
}
|
|
|
|
func contains(a []string, x string) bool {
|
|
for _, n := range a {
|
|
if x == n {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func logOut(client *mautrix.Client, roomID string, leave bool) error {
|
|
stopMailChecker(roomID)
|
|
deleteRoomAndEmailByRoomID(roomID)
|
|
if leave {
|
|
_, err := client.LeaveRoom(id.RoomID(roomID))
|
|
if err != nil {
|
|
WriteLog(critical, "#65 bot can't leave room: "+err.Error())
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func startMatrixSync(client *mautrix.Client) {
|
|
fmt.Println(client.UserID)
|
|
|
|
syncer := client.Syncer.(*mautrix.DefaultSyncer)
|
|
syncer.OnEventType(event.StateJoinRules, func(source mautrix.EventSource, evt *event.Event) {
|
|
host, err := getHostFromMatrixID(string(evt.Sender))
|
|
if err == -1 {
|
|
listcontains := contains(viper.GetStringSlice("allowed_servers"), host)
|
|
if listcontains {
|
|
client.JoinRoom(string(evt.RoomID), "", nil)
|
|
client.SendText(evt.RoomID, "Hey you have invited me to a new room. Enter !login to bridge this room to a Mail account")
|
|
} else {
|
|
client.LeaveRoom(evt.RoomID)
|
|
WriteLog(info, string("Got invalid invite from "+evt.Sender+" reason: senders server not whitelisted! Adjust your config if you want to allow this host using me"))
|
|
return
|
|
}
|
|
} else {
|
|
WriteLog(critical, "")
|
|
}
|
|
})
|
|
|
|
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
|
|
if evt.Sender != client.UserID && evt.Content.AsMember().Membership == "leave" {
|
|
logOut(client, string(evt.RoomID), true)
|
|
}
|
|
})
|
|
|
|
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
|
|
if evt.Sender == client.UserID {
|
|
return
|
|
}
|
|
message := evt.Content.AsMessage().Body
|
|
roomID := evt.RoomID
|
|
|
|
if is, err := isUserWritingEmail(string(roomID)); is && err == nil {
|
|
writeTemp, err := getWritingTemp(string(roomID))
|
|
if err != nil {
|
|
WriteLog(critical, "#43 getWritingTemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #43")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
}
|
|
if len(strings.Trim(writeTemp.subject, " ")) == 0 {
|
|
if evt.Content.AsMessage().MsgType != event.MsgText {
|
|
client.SendText(roomID, "You have to send a text for subject!")
|
|
return
|
|
}
|
|
err = saveWritingtemp(string(roomID), "subject", message)
|
|
if err != nil {
|
|
WriteLog(critical, "#44 saveWritingtemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #44")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
}
|
|
client.SendText(roomID, "Now send me the content of the email. One message is one line. If you want to send or cancel enter !send or !cancel")
|
|
} else {
|
|
if message == "!send" {
|
|
account, err := getSMTPAccount(string(roomID))
|
|
if err != nil {
|
|
WriteLog(critical, "#52 saveWritingtemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #52")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
}
|
|
|
|
m := gomail.NewMessage()
|
|
m.SetHeader("From", account.username)
|
|
|
|
if strings.Contains(writeTemp.receiver, ",") {
|
|
recEmails := strings.Split(writeTemp.receiver, ",")
|
|
m.SetHeader("To", recEmails...)
|
|
} else {
|
|
m.SetHeader("To", writeTemp.receiver)
|
|
}
|
|
|
|
m.SetHeader("Subject", writeTemp.subject)
|
|
|
|
if writeTemp.markdown {
|
|
toSendText := string(markdown.ToHTML([]byte(writeTemp.body), nil, nil))
|
|
toSendText = strings.ReplaceAll(toSendText, "\r\n<h", "<h")
|
|
toSendText = strings.ReplaceAll(toSendText, "\n\n<h", "<h")
|
|
toSendText = strings.ReplaceAll(toSendText, ">\n\n", ">")
|
|
toSendText = strings.ReplaceAll(toSendText, "\r\n", "<br>")
|
|
m.SetBody("text/html", toSendText)
|
|
|
|
plainbody := writeTemp.body
|
|
plainbody = strings.ReplaceAll(plainbody, "<br>", "\r\n")
|
|
m.AddAlternative("text/plain", plainbody)
|
|
} else {
|
|
m.SetBody("text/plain", writeTemp.body)
|
|
}
|
|
|
|
attachments, err := getAttachments(writeTemp.pkID)
|
|
if err == nil {
|
|
for _, i := range attachments {
|
|
client.SendText(roomID, "Attaching file: "+i)
|
|
m.Attach(tempDir + i)
|
|
}
|
|
} else {
|
|
client.SendText(roomID, "coulnd't attach files: "+err.Error())
|
|
}
|
|
|
|
d := gomail.NewDialer(account.host, account.port, account.username, account.password)
|
|
if account.ignoreSSL {
|
|
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
|
}
|
|
client.SendText(roomID, "Sending...")
|
|
if err := d.DialAndSend(m); err != nil {
|
|
WriteLog(logError, "#46 DialAndSend: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #53\r\n"+err.Error())
|
|
removeSMTPAccount(string(roomID))
|
|
client.SendText(roomID, "To fix this errer you have to run !setup smtp .... again")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
}
|
|
client.SendText(roomID, "Message sent successfully")
|
|
deleteWritingTemp(string(roomID))
|
|
} else if message == "!cancel" {
|
|
client.SendText(roomID, "Mail canceled")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
} else if strings.HasPrefix(message, "!rm") && len(strings.Split(message, " ")) > 0 {
|
|
splitted := strings.Split(message, " ")[1:]
|
|
var fileName string
|
|
for _, a := range splitted {
|
|
fileName += a + " "
|
|
}
|
|
fileName = strings.TrimRight(fileName, " ")
|
|
fileName = strings.TrimLeft(fileName, " ")
|
|
fmt.Println(fileName)
|
|
err := deleteAttachment(fileName, writeTemp.pkID)
|
|
if err != nil {
|
|
client.SendText(roomID, "Couldn't delete attachment: "+err.Error())
|
|
return
|
|
}
|
|
_ = os.Remove(tempDir + fileName)
|
|
client.SendText(roomID, "Attachment deleted!")
|
|
|
|
} else {
|
|
if evt.Content.AsMessage().MsgType == event.MsgText {
|
|
if len(strings.ReplaceAll(writeTemp.body, " ", "")) == 0 {
|
|
err = saveWritingtemp(string(roomID), "body", message+"\r\n")
|
|
} else {
|
|
err = saveWritingtemp(string(roomID), "body", writeTemp.body+message+"\r\n")
|
|
}
|
|
if err != nil {
|
|
WriteLog(critical, "#54 saveWritingtemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #54")
|
|
deleteWritingTemp(string(roomID))
|
|
return
|
|
}
|
|
} else if evt.Content.AsMessage().MsgType == event.MsgFile || evt.Content.AsMessage().MsgType == event.MsgImage {
|
|
if strings.HasPrefix(string(evt.Content.AsMessage().URL), "mxc://") {
|
|
reader, err := client.Download(id.MustParseContentURI(evt.Content.AsMessage().Body))
|
|
if err != nil {
|
|
client.SendText(roomID, "Couldn't download File: "+err.Error())
|
|
} else {
|
|
filename := strconv.Itoa(int(time.Now().Unix())) + "_" + evt.Content.AsMessage().Body
|
|
err := streamToTempFile(reader, filename)
|
|
if err != nil {
|
|
client.SendText(roomID, "Couldn't download file: "+err.Error())
|
|
} else {
|
|
addEmailAttachment(writeTemp.pkID, filename)
|
|
client.SendText(roomID, "File "+filename+" attached!")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
} else if err != nil {
|
|
WriteLog(critical, "#41 deleteWritingTemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #41")
|
|
return
|
|
} else {
|
|
//commands only available in room not bridged to email
|
|
if message == "!login" {
|
|
client.SendText(roomID, "Okay send me the data of your server(at first IMAPs) in the given order, splitted by a comma(,)\r\n!setup imap, host:port, username/email, password, mailbox, ignoreSSL\r\n!setup smtp, host, port, email, password, ignoreSSL\r\n\r\nExample: \r\n!setup imap, host.com:993, mail@host.com, w0rdp4ss, INBOX, false\r\nor\r\n!setup smtp, host.com:587, mail@host.com, w0rdp4ss, false")
|
|
} else if strings.HasPrefix(message, "!setup") {
|
|
data := strings.Trim(strings.ReplaceAll(message, "!setup", ""), " ")
|
|
s := strings.Split(data, ",")
|
|
if len(s) < 4 || len(s) > 6 {
|
|
client.SendText(roomID, "Wrong syntax :/\r\nExample: \r\n!setup imap, host.com:993, mail@host.com, w0rdp4ss, INBOX, false\r\nor\r\n"+
|
|
"!setup smtp, host.com:587, mail@host.com, w0rdp4ss, false")
|
|
} else {
|
|
accountType := s[0]
|
|
if strings.ToLower(accountType) != "imap" && strings.ToLower(accountType) != "smtp" {
|
|
client.SendText(roomID, "What? you can setup 'imap' and 'smtp', not \""+accountType+"\"")
|
|
return
|
|
}
|
|
host := strings.ReplaceAll(s[1], " ", "")
|
|
username := strings.ReplaceAll(s[2], " ", "")
|
|
password := strings.ReplaceAll(s[3], " ", "")
|
|
ignoreSSlCert := false
|
|
mailbox := "INBOX"
|
|
if len(s) >= 5 {
|
|
mailbox = strings.ReplaceAll(s[4], " ", "")
|
|
}
|
|
var err error
|
|
defaultMailSyncInterval := viper.GetInt("defaultmailCheckInterval")
|
|
imapAccID, smtpAccID, erro := getRoomAccounts(string(roomID))
|
|
if erro != nil {
|
|
client.SendText(roomID, "Something went wrong! Contact the admin. Errorcode: #37")
|
|
WriteLog(critical, "#37 checking getRoomAccounts: "+erro.Error())
|
|
return
|
|
}
|
|
if accountType == "imap" {
|
|
if len(s) == 6 {
|
|
ignoreSSlCert, err = strconv.ParseBool(strings.ReplaceAll(s[5], " ", ""))
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
ignoreSSlCert = false
|
|
}
|
|
}
|
|
if imapAccID != -1 {
|
|
client.SendText(roomID, "IMAP account already existing. Create a new room if you want to use a different account!")
|
|
return
|
|
}
|
|
isInUse, err := isImapAccountAlreadyInUse(username)
|
|
if err != nil {
|
|
client.SendText(roomID, "Something went wrong! Contact the admin. Errorcode: #03")
|
|
WriteLog(critical, "#03 checking isImapAccountAlreadyInUse: "+err.Error())
|
|
return
|
|
}
|
|
|
|
if isInUse {
|
|
client.SendText(roomID, "This email is already in Use! You cannot use your email twice!")
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
if !strings.Contains(host, ":") {
|
|
host += ":993"
|
|
}
|
|
|
|
mclient, err := loginMail(host, username, password, ignoreSSlCert)
|
|
if mclient != nil && err == nil {
|
|
has, er := hasRoom(string(roomID))
|
|
if er != nil {
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #25")
|
|
WriteLog(critical, "checking imapAcc #25: "+er.Error())
|
|
return
|
|
}
|
|
var newRoomID int64
|
|
if !has {
|
|
newRoomID = insertNewRoom(string(roomID), defaultMailSyncInterval)
|
|
if newRoomID == -1 {
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #26")
|
|
WriteLog(critical, "checking insertNewRoom #26")
|
|
return
|
|
}
|
|
} else {
|
|
id, err := getRoomPKID(evt.RoomID.String())
|
|
if err != nil {
|
|
WriteLog(critical, "checking getRoomPKID #27: "+err.Error())
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #27")
|
|
return
|
|
}
|
|
newRoomID = int64(id)
|
|
}
|
|
imapID, succes := insertimapAccountount(host, username, password, mailbox, ignoreSSlCert)
|
|
if !succes {
|
|
client.SendText(roomID, "sth went wrong. Contact your admin")
|
|
return
|
|
}
|
|
err = saveImapAcc(string(roomID), int(imapID))
|
|
if err != nil {
|
|
WriteLog(critical, "saveImapAcc #35 : "+err.Error())
|
|
client.SendText(roomID, "sth went wrong. Contact you admin! Errorcode: #35")
|
|
return
|
|
}
|
|
client.SendText(roomID, "Bridge created successfully!\r\nYou should delete the message containing your credentials ;)\r\nIMAP:\r\n"+
|
|
"host: "+host+"\r\n"+
|
|
"username: "+username+"\r\n"+
|
|
"mailbox: "+mailbox+"\r\n"+
|
|
"ignoreSSL: "+strconv.FormatBool(ignoreSSlCert))
|
|
|
|
startMailListener(imapAccountount{host, username, password, roomID.String(), mailbox, ignoreSSlCert, int(newRoomID), defaultMailSyncInterval, true})
|
|
WriteLog(success, "Created new bridge and started maillistener\r\n")
|
|
} else {
|
|
client.SendText(roomID, "Error creating bridge! Errorcode: #04\r\nReason: "+err.Error())
|
|
WriteLog(logError, "#04 creating bridge: "+err.Error())
|
|
}
|
|
}()
|
|
} else if accountType == "smtp" {
|
|
if smtpAccID != -1 {
|
|
client.SendText(roomID, "SMTP account already existing. Create a new room if you want to use a different account!")
|
|
return
|
|
}
|
|
isInUse, err := isSMTPAccountAlreadyInUse(username)
|
|
if err != nil {
|
|
client.SendText(roomID, "Something went wrong! Contact the admin. Errorcode: #24")
|
|
WriteLog(critical, "#24 checking isSMTPAccountAlreadyInUse: "+err.Error())
|
|
return
|
|
}
|
|
if isInUse {
|
|
client.SendText(roomID, "This smtp-username is already in Use! You cannot use your email twice!")
|
|
return
|
|
}
|
|
|
|
go func() {
|
|
if len(s) == 5 {
|
|
ignoreSSlCert, err = strconv.ParseBool(strings.ReplaceAll(s[4], " ", ""))
|
|
if err != nil {
|
|
fmt.Println(err.Error())
|
|
ignoreSSlCert = false
|
|
}
|
|
}
|
|
has, er := hasRoom(roomID.String())
|
|
if er != nil {
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #28")
|
|
WriteLog(critical, "checking imapAcc #28: "+er.Error())
|
|
return
|
|
}
|
|
var newRoomID int64
|
|
if !has {
|
|
newRoomID = insertNewRoom(roomID.String(), defaultMailSyncInterval)
|
|
if newRoomID == -1 {
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #29")
|
|
WriteLog(critical, "checking insertNewRoom #29: ")
|
|
return
|
|
}
|
|
} else {
|
|
id, err := getRoomPKID(evt.RoomID.String())
|
|
if err != nil {
|
|
WriteLog(critical, "checking getRoomPKID #30: "+err.Error())
|
|
client.SendText(roomID, "An error occured! contact your admin! Errorcode: #30")
|
|
return
|
|
}
|
|
newRoomID = int64(id)
|
|
}
|
|
port := 587
|
|
if !strings.Contains(host, ":") {
|
|
client.SendText(roomID, "No port specified! Using 587")
|
|
} else {
|
|
hostsplit := strings.Split(host, ":")
|
|
host = hostsplit[0]
|
|
port, err = strconv.Atoi(strings.Trim(hostsplit[1], " "))
|
|
if err != nil {
|
|
client.SendText(roomID, "The port must be a number!")
|
|
return
|
|
}
|
|
}
|
|
smtpID, err := insertSMTPAccountount(host, port, username, password, ignoreSSlCert)
|
|
if err != nil {
|
|
client.SendText(roomID, "sth went wrong. Contact your admin")
|
|
return
|
|
}
|
|
err = saveSMTPAcc(roomID.String(), int(smtpID))
|
|
if err != nil {
|
|
WriteLog(critical, "saveSMTPAcc #36 : "+err.Error())
|
|
client.SendText(roomID, "sth went wrong. Contact you admin! Errorcode: #34")
|
|
return
|
|
}
|
|
|
|
client.SendText(roomID, "SMTP data saved.\r\nSMTP:\r\n"+
|
|
"host: "+host+"\r\n"+
|
|
"port: "+strconv.Itoa(port)+"\r\n"+
|
|
"username: "+username+"\r\n"+
|
|
"ignoreSSL: "+strconv.FormatBool(ignoreSSlCert))
|
|
}()
|
|
} else {
|
|
client.SendText(roomID, "Not implemented yet!")
|
|
}
|
|
}
|
|
} else if message == "!help" {
|
|
helpText := "-------- Help --------\r\n"
|
|
helpText += "!setup imap/smtp, host:port, username(em@ail.com), password, <mailbox (only for imap)>, ignoreSSLcert(true/false) - creates a bridge for this room\r\n"
|
|
helpText += "!ping - gets information about the email bridge for this room\r\n"
|
|
helpText += "!help - shows this command help overview\r\n"
|
|
helpText += "!write (receiver(s) email(s) splitted by space!) <markdown default:true>- sends an email to a given address\r\n"
|
|
helpText += "!mailboxes - shows a list with all mailboxes available on your IMAP server\r\n"
|
|
helpText += "!setmailbox (mailbox) - changes the mailbox for the room\r\n"
|
|
helpText += "!mailbox - shows the currently selected mailbox\r\n"
|
|
helpText += "!sethtml (on/off or true/false) - sets HTML-rendering for messages on/off\r\n"
|
|
helpText += "!logout remove email bridge from current room\r\n"
|
|
helpText += "!leave unbridge the current room and kick the bot\r\n"
|
|
helpText += "\r\n---- Email writing commands ----\r\n"
|
|
helpText += "!send - sends the email\r\n"
|
|
helpText += "!rm <file> - removes given attachment from email\r\n"
|
|
client.SendText(roomID, helpText)
|
|
} else if message == "!ping" {
|
|
if has, err := hasRoom(roomID.String()); has && err == nil {
|
|
roomData, err := getRoomInfo(roomID.String())
|
|
if err != nil {
|
|
WriteLog(logError, "#006 getRoomInfo: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured")
|
|
return
|
|
}
|
|
|
|
client.SendText(roomID, roomData)
|
|
} else {
|
|
if err != nil {
|
|
WriteLog(logError, "#06 hasRoom: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured")
|
|
} else {
|
|
client.SendText(roomID, "You have to login to use this command!")
|
|
}
|
|
}
|
|
} else if strings.HasPrefix(message, "!write") {
|
|
if has, err := hasRoom(roomID.String()); has && err == nil {
|
|
_, smtpAccID, erro := getRoomAccounts(roomID.String())
|
|
if erro != nil {
|
|
WriteLog(critical, "#38 getRoomAccounts: "+erro.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #38")
|
|
return
|
|
}
|
|
if smtpAccID == -1 {
|
|
client.SendText(roomID, "You have to setup an smtp account. Type !help or !login for more information")
|
|
return
|
|
}
|
|
s := strings.Split(message, " ")
|
|
if len(s) > 1 {
|
|
receiver := strings.Trim(s[1], " ")
|
|
if len(s) > 2 {
|
|
receiverString := ""
|
|
for i := 1; i < len(s); i++ {
|
|
recEmail := strings.Trim(s[i], " ")
|
|
if len(recEmail) == 0 || !strings.Contains(recEmail, "@") || !strings.Contains(recEmail, ".") || strings.Contains(receiverString, recEmail) {
|
|
continue
|
|
}
|
|
add := ","
|
|
if strings.HasSuffix(recEmail, ",") {
|
|
add = ""
|
|
}
|
|
receiverString += strings.Trim(s[i], " ") + add
|
|
}
|
|
receiver = receiverString[:len(receiverString)-1]
|
|
}
|
|
|
|
if strings.Contains(receiver, "@") && strings.Contains(receiver, ".") && len(receiver) > 5 {
|
|
hasTemp, err := isUserWritingEmail(roomID.String())
|
|
if err != nil {
|
|
WriteLog(critical, "#39 isUserWritingEmail: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #39")
|
|
return
|
|
}
|
|
if hasTemp {
|
|
er := deleteWritingTemp(roomID.String())
|
|
if er != nil {
|
|
WriteLog(critical, "#40 deleteWritingTemp: "+er.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #40")
|
|
return
|
|
}
|
|
}
|
|
|
|
mrkdwn := 0
|
|
if viper.GetBool("markdownEnabledByDefault") {
|
|
mrkdwn = 1
|
|
}
|
|
if len(s) == 3 {
|
|
mdwn, berr := strconv.ParseBool(s[2])
|
|
if berr == nil {
|
|
if mdwn {
|
|
mrkdwn = 1
|
|
} else {
|
|
mrkdwn = 0
|
|
}
|
|
}
|
|
}
|
|
|
|
err = newWritingTemp(roomID.String(), receiver)
|
|
saveWritingtemp(roomID.String(), "markdown", strconv.Itoa(mrkdwn))
|
|
if err != nil {
|
|
WriteLog(critical, "#42 newWritingTemp: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #42")
|
|
return
|
|
}
|
|
client.SendText(roomID, "Now send me the subject of your email")
|
|
} else {
|
|
client.SendText(roomID, "this is an email: max@google.de\r\nthis is no email: "+receiver)
|
|
}
|
|
} else {
|
|
client.SendText(roomID, "Usage: !write <emailaddress>")
|
|
}
|
|
} else {
|
|
client.SendText(roomID, "You have to login to use this command!")
|
|
}
|
|
} else if strings.HasPrefix(message, "!setmailbox") {
|
|
imapAccID, _, erro := getRoomAccounts(roomID.String())
|
|
if erro != nil {
|
|
WriteLog(critical, "#48 getRoomAccounts: "+erro.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #48")
|
|
return
|
|
}
|
|
if imapAccID != -1 {
|
|
d := strings.Split(message, " ")
|
|
if len(d) == 2 {
|
|
mailbox := d[1]
|
|
saveMailbox(roomID.String(), mailbox)
|
|
deleteMails(roomID.String())
|
|
stopMailChecker(roomID.String())
|
|
imapAccount, err := getIMAPAccount(roomID.String())
|
|
if err != nil {
|
|
WriteLog(critical, "#49 getIMAPAccount: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #49")
|
|
return
|
|
}
|
|
imapAccount.silence = true
|
|
go startMailListener(*imapAccount)
|
|
client.SendText(roomID, "Mailbox updated")
|
|
} else {
|
|
client.SendText(roomID, "Usage: !setmailbox <new mailbox>")
|
|
}
|
|
} else {
|
|
client.SendText(roomID, "You have to setup an IMAP account to use this command. Use !setup or !login for more informations")
|
|
}
|
|
} else if strings.HasPrefix(message, "!sethtml") {
|
|
imapAccID, _, erro := getRoomAccounts(roomID.String())
|
|
if erro != nil {
|
|
WriteLog(critical, "#50 getRoomAccounts: "+erro.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #50")
|
|
return
|
|
}
|
|
if imapAccID != -1 {
|
|
d := strings.Split(message, " ")
|
|
if len(d) == 2 {
|
|
newMode := strings.ToLower(d[1])
|
|
newModeB := false
|
|
if newMode == "true" || newMode == "on" {
|
|
newModeB = true
|
|
} else if newMode != "false" && newMode != "off" {
|
|
client.SendText(roomID, "What?\r\non/off or true/false")
|
|
return
|
|
}
|
|
err := setHTMLenabled(roomID.String(), newModeB)
|
|
if err != nil {
|
|
WriteLog(critical, "#56 getMailbox: "+err.Error())
|
|
client.SendText(roomID, "An server-error occured Errorcode: #56")
|
|
return
|
|
}
|
|
client.SendText(roomID, "Successfully set HTML-rendering to "+newMode)
|
|
} else {
|
|
client.SendText(roomID, "Usage: !sethtml (on/of) or (true/false)")
|
|
}
|
|
} else {
|
|
client.SendText(roomID, "You have to setup an IMAP account to use this command. Use !setup or !login for more informations")
|
|
}
|
|
} else if message == "!logout" {
|
|
err := logOut(client, roomID.String(), false)
|
|
if err != nil {
|
|
client.SendText(roomID, "Error logging out: "+err.Error())
|
|
} else {
|
|
client.SendText(roomID, "Successfully logged out")
|
|
}
|
|
} else if message == "!leave" {
|
|
err := logOut(client, roomID.String(), true)
|
|
if err != nil {
|
|
client.SendText(roomID, "Error leaving: "+err.Error())
|
|
} else {
|
|
client.SendText(roomID, "Successfully unbridged")
|
|
}
|
|
|
|
} else if strings.HasPrefix(message, "!blocklist") || strings.HasPrefix(message, "!bl") {
|
|
imapAccID, _, _ := getRoomAccounts(roomID.String())
|
|
if imapAccID == -1 {
|
|
client.SendText(roomID, "You need to login with an imap account to use this command!")
|
|
return
|
|
}
|
|
sm := strings.Split(message, " ")
|
|
if len(sm) < 3 {
|
|
if len(sm) == 2 && (sm[1] == "view" || sm[1] == "list") {
|
|
viewBlocklist(roomID.String(), client)
|
|
} else if len(sm) == 2 && sm[1] == "clear" {
|
|
err := clearBlocklist(imapAccID)
|
|
var msg string
|
|
if err != nil {
|
|
fmt.Println("Err:", err.Error())
|
|
msg = "Error clearing blocklist! View logs for more details!"
|
|
} else {
|
|
msg = "Blocklist is now clean!"
|
|
}
|
|
client.SendText(roomID, msg)
|
|
} else {
|
|
client.SendText(roomID, "Usage: !blocklist <add/delete/clear/view> <email address>\nDon't show any emails from a given email address.\nWildcards (like *@evilEmailAddress.com) are supported")
|
|
}
|
|
} else {
|
|
cmd := strings.ToLower(sm[1])
|
|
addr := sm[2]
|
|
if !strings.Contains(addr, "@") || !strings.Contains(addr, ".") || len(addr) < 6 {
|
|
client.SendText(roomID, "Error! "+addr+" is an invalid email address!")
|
|
} else {
|
|
switch cmd {
|
|
case "add":
|
|
{
|
|
//add item to blocklis
|
|
err := addEmailToBlocklist(imapAccID, addr)
|
|
var msg string
|
|
if err != nil {
|
|
fmt.Println("Err:", err.Error())
|
|
msg = "Error adding " + addr + " to blocklist! View logs for more details!"
|
|
} else {
|
|
msg = "Success adding " + addr + " to blocklist!"
|
|
}
|
|
client.SendText(roomID, msg)
|
|
}
|
|
case "remove", "delete", "rm":
|
|
{
|
|
err := removeEmailFromBlocklist(imapAccID, addr)
|
|
var msg string
|
|
if err != nil {
|
|
fmt.Println("Err:", err.Error())
|
|
msg = "Error deleting " + addr + " from blocklist! View logs for more details!"
|
|
} else {
|
|
msg = "Success deleting " + addr + " from blocklist!"
|
|
}
|
|
client.SendText(roomID, msg)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else if strings.HasPrefix(message, "!view") {
|
|
imapAccID, _, _ := getRoomAccounts(roomID.String())
|
|
if imapAccID == -1 {
|
|
client.SendText(roomID, "You need to login with an imap account to use this command!")
|
|
return
|
|
}
|
|
sm := strings.Split(message, " ")
|
|
if len(sm) == 1 {
|
|
viewViewHelp(roomID.String(), client)
|
|
} else if len(sm) > 1 {
|
|
switch strings.ToLower(sm[1]) {
|
|
case "mb", "mailbox":
|
|
{
|
|
viewMailbox(roomID.String(), client)
|
|
}
|
|
case "mbs", "mailboxes":
|
|
{
|
|
viewMailboxes(roomID.String(), client)
|
|
}
|
|
case "blocklist", "bl", "blocklists", "blo", "blocked":
|
|
{
|
|
viewBlocklist(roomID.String(), client)
|
|
}
|
|
case "h", "help":
|
|
{
|
|
viewViewHelp(roomID.String(), client)
|
|
}
|
|
default:
|
|
{
|
|
viewViewHelp(roomID.String(), client)
|
|
}
|
|
}
|
|
}
|
|
} else if strings.HasPrefix(message, "!") {
|
|
client.SendText(roomID, "command not found!")
|
|
}
|
|
}
|
|
})
|
|
|
|
err := client.Sync()
|
|
if err != nil {
|
|
WriteLog(logError, "#07 Syncing: "+err.Error())
|
|
fmt.Println(err)
|
|
}
|
|
}
|
|
|
|
func viewViewHelp(roomID string, client *mautrix.Client) {
|
|
client.SendText(id.RoomID(roomID), "Available options:\n\nmb/mailbox\t-\tViews the current used mailbox\nmbs/mailboxes\t-\tView the available mailboxes\nbl/blocklist\t-\tViews the list of blocked addresses")
|
|
}
|
|
|
|
func deleteTempFile(name string) {
|
|
os.Remove(tempDir + name)
|
|
}
|
|
|
|
func streamToTempFile(stream io.ReadCloser, file string) error {
|
|
if _, err := os.Stat(tempDir); os.IsNotExist(err) {
|
|
os.Mkdir(tempDir, os.ModePerm)
|
|
}
|
|
fo, err := os.Create(tempDir + file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err := fo.Close(); err != nil {
|
|
|
|
}
|
|
}()
|
|
|
|
buf := make([]byte, 1024)
|
|
for {
|
|
n, err := stream.Read(buf)
|
|
if err != nil && err != io.EOF {
|
|
return err
|
|
}
|
|
if n == 0 {
|
|
break
|
|
}
|
|
|
|
if _, err := fo.Write(buf[:n]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func main() {
|
|
dirPrefix = "./"
|
|
if len(os.Getenv("BRIDGE_DATA_PATH")) > 0 {
|
|
argDir := os.Getenv("BRIDGE_DATA_PATH")
|
|
if !strings.HasPrefix(argDir, "/") && !strings.HasPrefix(argDir, "./") {
|
|
argDir = "./" + argDir
|
|
}
|
|
if !strings.HasSuffix(argDir, "/") {
|
|
argDir = argDir + "/"
|
|
}
|
|
s, err := os.Stat(argDir)
|
|
if err != nil {
|
|
err = os.Mkdir(argDir, 0750)
|
|
if err != nil {
|
|
fmt.Printf("Error creating dir %s\n%s\n", argDir, err.Error())
|
|
os.Exit(1)
|
|
return
|
|
}
|
|
} else {
|
|
if !s.IsDir() {
|
|
fmt.Printf("%s is not a dir!\n", argDir)
|
|
os.Exit(1)
|
|
return
|
|
}
|
|
}
|
|
dirPrefix = argDir
|
|
}
|
|
tempDir = dirPrefix + tempDir
|
|
|
|
initLogger()
|
|
|
|
er := initDB()
|
|
if er == nil {
|
|
createAllTables()
|
|
exit := initCfg()
|
|
if exit {
|
|
return
|
|
}
|
|
WriteLog(success, "create tables")
|
|
handleDBVersion()
|
|
} else {
|
|
WriteLog(critical, "#08 creating tables: "+er.Error())
|
|
panic(er)
|
|
}
|
|
|
|
deleteAllWritingTemps()
|
|
|
|
loginMatrix()
|
|
|
|
startMailSchedeuler()
|
|
|
|
for {
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}
|
|
|
|
func stopMailChecker(roomID string) {
|
|
_, ok := listenerMap[roomID]
|
|
if ok {
|
|
close(listenerMap[roomID])
|
|
//delete(listenerMap, evt.RoomID)
|
|
}
|
|
}
|
|
|
|
var listenerMap map[string]chan bool
|
|
var clients map[string]*client.Client
|
|
var imapErrors map[string]*imapError
|
|
var checksPerAccount map[string]int
|
|
|
|
const maxRoomChecks = 15
|
|
|
|
const maxErrUntilReconnect = 10
|
|
|
|
type imapError struct {
|
|
retryCount, loginErrCount int
|
|
}
|
|
|
|
func getChecksForAccount(roomID string) int {
|
|
checks, ok := checksPerAccount[roomID]
|
|
if ok {
|
|
return checks
|
|
}
|
|
checksPerAccount[roomID] = 0
|
|
return 0
|
|
}
|
|
|
|
func hasError(roomID string) (has bool, count int) {
|
|
_, ok := imapErrors[roomID]
|
|
if ok {
|
|
return true, imapErrors[roomID].retryCount
|
|
}
|
|
return false, -1
|
|
}
|
|
|
|
func startMailSchedeuler() {
|
|
listenerMap = make(map[string]chan bool)
|
|
clients = make(map[string]*client.Client)
|
|
imapErrors = make(map[string]*imapError)
|
|
checksPerAccount = make(map[string]int)
|
|
|
|
accounts, err := getimapAccounts()
|
|
if err != nil {
|
|
WriteLog(critical, "#09 reading accounts: "+err.Error())
|
|
log.Panic(err)
|
|
}
|
|
for i := 0; i < len(accounts); i++ {
|
|
go startMailListener(accounts[i])
|
|
}
|
|
WriteLog(success, "started "+strconv.Itoa(len(accounts))+" mail listener")
|
|
}
|
|
|
|
func startMailListener(account imapAccountount) {
|
|
quit := make(chan bool)
|
|
connectSuccess := false
|
|
var mClient *client.Client
|
|
var err error
|
|
for !connectSuccess {
|
|
mClient, err = loginMail(account.host, account.username, account.password, account.ignoreSSL)
|
|
if err == nil {
|
|
connectSuccess = true
|
|
continue
|
|
} else {
|
|
WriteLog(info, "couldn't connect to imap server try again n a some minutes: "+err.Error())
|
|
time.Sleep(1 * time.Minute)
|
|
}
|
|
}
|
|
|
|
listenerMap[account.roomID] = quit
|
|
clients[account.roomID] = mClient
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-quit:
|
|
return
|
|
default:
|
|
if getChecksForAccount(account.roomID) >= maxRoomChecks {
|
|
reconnect(account)
|
|
return
|
|
}
|
|
fetchNewMails(mClient, &account)
|
|
checksPerAccount[account.roomID]++
|
|
time.Sleep((time.Duration)(account.mailCheckInterval) * time.Second)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func reconnect(account imapAccountount) {
|
|
WriteLog(info, "reconnecting account "+account.username)
|
|
checksPerAccount[account.roomID] = 0
|
|
stopMailChecker(account.roomID)
|
|
nacc := account
|
|
go startMailListener(nacc)
|
|
}
|
|
|
|
func fetchNewMails(mClient *client.Client, account *imapAccountount) {
|
|
messages := make(chan *imap.Message, 1)
|
|
section, errCode := getMails(mClient, account.mailbox, messages)
|
|
|
|
if section == nil {
|
|
if errCode == 0 {
|
|
haserr, errCount := hasError(account.roomID)
|
|
if haserr {
|
|
if imapErrors[account.roomID].loginErrCount > 15 {
|
|
WriteLog(logError, "Youve got too much errors for the emailaccount: "+account.username)
|
|
}
|
|
if errCount < maxErrUntilReconnect {
|
|
imapErrors[account.roomID].retryCount++
|
|
} else {
|
|
imapErrors[account.roomID].retryCount = 0
|
|
imapErrors[account.roomID].loginErrCount++
|
|
reconnect(*account)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if account.silence {
|
|
account.silence = false
|
|
}
|
|
return
|
|
}
|
|
|
|
for msg := range messages {
|
|
mailID := msg.Envelope.Subject + strconv.Itoa(int(msg.InternalDate.Unix()))
|
|
if has, err := dbContainsMail(mailID, account.roomPKID); !has && err == nil {
|
|
go insertEmail(mailID, account.roomPKID)
|
|
if !account.silence {
|
|
handleMail(msg, section, *account)
|
|
}
|
|
} else if err != nil {
|
|
WriteLog(logError, "#11 dbContains mail: "+err.Error())
|
|
fmt.Println(err.Error())
|
|
}
|
|
}
|
|
if account.silence {
|
|
account.silence = false
|
|
}
|
|
}
|
|
|
|
func handleMail(mail *imap.Message, section *imap.BodySectionName, account imapAccountount) {
|
|
content := getMailContent(mail, section, account.roomID)
|
|
if content == nil {
|
|
return
|
|
}
|
|
for _, senderMail := range content.sendermails {
|
|
fmt.Println("checking", senderMail)
|
|
if checkForBlocklist(account.roomID, senderMail) {
|
|
fmt.Println("blocked email from ", senderMail)
|
|
return
|
|
}
|
|
}
|
|
from := html.EscapeString(content.from)
|
|
fmt.Println("attachments: " + content.attachment)
|
|
headerContent := &event.MessageEventContent{
|
|
Format: event.FormatHTML,
|
|
Body: "\r\n────────────────────────────────────\r\n## You've got a new Email from " + from + "\r\n" + "Subject: " + content.subject + "\r\n" + "────────────────────────────────────",
|
|
FormattedBody: "<br>────────────────────────────────────<br><b> You've got a new Email</b> from <b>" + from + "</b><br>" + "Subject: " + content.subject + "<br>" + "────────────────────────────────────",
|
|
MsgType: event.MsgText,
|
|
}
|
|
|
|
matrixClient.SendMessageEvent(id.RoomID(account.roomID), event.EventMessage, &headerContent)
|
|
|
|
if content.htmlFormat {
|
|
bodyContent := &event.MessageEventContent{
|
|
Format: event.FormatHTML,
|
|
Body: content.body,
|
|
FormattedBody: string(markdown.ToHTML([]byte(content.body), nil, nil)),
|
|
MsgType: event.MsgText,
|
|
}
|
|
matrixClient.SendMessageEvent(id.RoomID(account.roomID), event.EventMessage, &bodyContent)
|
|
} else {
|
|
matrixClient.SendText(id.RoomID(account.roomID), content.body)
|
|
}
|
|
}
|