init commit

This commit is contained in:
lyx0 2024-01-09 03:20:42 +01:00
parent d8436afd6c
commit a9b9c12fca
20 changed files with 188 additions and 779 deletions

View file

@ -1,7 +0,0 @@
BINARY_NAME=NourybotMatrix.out
cup:
sudo docker compose up
xd:
cd cmd/nourybot && go build -o ${BINARY_NAME} && ./${BINARY_NAME}

View file

@ -1,11 +0,0 @@
MATRIX_HOMESERVER=matrix.cooldomain.com
MATRIX_USERNAME=nourybot
MATRIX_PASSWORD=MyCoolPassword_42069xD
SQLITE_DATABASE=mautrix-example.db
OWM_KEY=MyCoolOpenWeatherMapApiKey
LAST_FM_APPLICATION_NAME=nourybot
LAST_FM_API_KEY=MyCoolLastFmApiKey
LAST_FM_SECRET=MyCoolLastFmSecretKey

View file

@ -1,145 +0,0 @@
package main
import (
"strings"
"github.com/lyx0/nourybot-matrix/internal/commands"
"github.com/lyx0/nourybot-matrix/internal/common"
"maunium.net/go/mautrix/event"
)
func (app *Application) ParseCommand(evt *event.Event) {
common.CommandUsed()
// commandName is the actual name of the command without the prefix.
// e.g. `!ping` would be `ping`.
commandName := strings.ToLower(strings.SplitN(evt.Content.AsMessage().Body, " ", 2)[0][1:])
// cmdParams are additional command parameters.
// e.g. `!weather san antonio`
// cmdParam[0] is `san` and cmdParam[1] = `antonio`.
cmdParams := strings.SplitN(evt.Content.AsMessage().Body, " ", 500)
app.Log.Info().Msgf("cmdParams: %s", cmdParams)
// msgLen is the amount of words in a message without the prefix.
// Useful to check if enough cmdParams are provided.
msgLen := len(strings.SplitN(evt.Content.AsMessage().Body, " ", -2))
app.Log.Info().Msgf("Command: %s", commandName)
switch commandName {
case "xd":
app.SendText(evt, "XD !")
return
case "currency":
if msgLen < 4 {
app.SendText(evt, "Not enough arguments provided")
return
} else {
if resp, err := commands.Currency(cmdParams[1], cmdParams[2], cmdParams[4]); err != nil {
app.Log.Error().Err(err).Msg("failed to handle currency command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
case "lastfm":
if msgLen == 1 {
app.SendText(evt, "Not enough arguments provided")
return
} else {
if resp, err := commands.LastFmUserRecent(cmdParams[1]); err != nil {
app.Log.Error().Err(err).Msg("failed to handle lastfm command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
case "phonetic":
if msgLen == 1 {
if resp, err := commands.Help(commandName); err != nil {
app.Log.Error().Err(err).Msg("failed to handle help->phonetic command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
} else {
msg := evt.Content.AsMessage().Body[9:len(evt.Content.AsMessage().Body)]
if resp, err := commands.Phonetic(msg); err != nil {
app.Log.Error().Err(err).Msg("failed to handle phonetic command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
case "ping":
if resp, err := commands.Ping(); err != nil {
app.Log.Error().Err(err).Msg("failed to handle ping command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
case "random":
if msgLen == 2 && cmdParams[1] == "xkcd" {
if resp, err := commands.RandomXkcd(); err != nil {
app.Log.Error().Err(err).Msg("failed to handle random->xkcd command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
case "xkcd":
if msgLen == 2 && cmdParams[1] == "random" {
if resp, err := commands.RandomXkcd(); err != nil {
app.Log.Error().Err(err).Msg("failed to handle xkcd->randomXkcd command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
} else if msgLen == 1 {
if resp, err := commands.Xkcd(); err != nil {
app.Log.Error().Err(err).Msg("failed to handle xkcd command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
case "weather":
if msgLen < 2 {
app.SendText(evt, "Not enough arguments provided")
return
} else {
location := evt.Content.AsMessage().Body[9:len(evt.Content.AsMessage().Body)]
if resp, err := commands.Weather(location); err != nil {
app.Log.Error().Err(err).Msg("Failed to handle Weather command")
app.SendText(evt, "Something went wrong.")
return
} else {
app.SendText(evt, resp)
return
}
}
}
}

View file

@ -1,14 +0,0 @@
package main
import "maunium.net/go/mautrix/event"
func (app *Application) SendText(evt *event.Event, message string) {
room := evt.RoomID
resp, err := app.Mc.SendText(room, message)
if err != nil {
app.Log.Error().Err(err).Msg("Failed to send event")
} else {
app.Log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
}
}

View file

@ -1,11 +0,0 @@
package main
import (
"maunium.net/go/mautrix/event"
)
func (app *Application) ParseEvent(evt *event.Event) {
// TODO:
// Log the events or whatever, I don't even know what events there all are rn.
app.Log.Info().Msgf("Event: %s", evt.Content.AsMessage().Body)
}

View file

@ -1,175 +0,0 @@
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"sync"
"time"
"github.com/chzyer/readline"
"github.com/joho/godotenv"
"github.com/lyx0/nourybot-matrix/internal/common"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
type config struct {
matrixHomeserver string
matrixUser string
matrixPass string
database string
}
type Application struct {
Mc *mautrix.Client
Log zerolog.Logger
}
var debug = flag.Bool("debug", false, "Enable debug logs")
func main() {
flag.Parse()
err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env")
}
var cfg config
cfg.matrixHomeserver = os.Getenv("MATRIX_HOMESERVER")
cfg.matrixUser = os.Getenv("MATRIX_USERNAME")
cfg.matrixPass = os.Getenv("MATRIX_PASSWORD")
cfg.database = os.Getenv("SQLITE_DATABASE")
client, err := mautrix.NewClient(cfg.matrixHomeserver, "", "")
if err != nil {
panic(err)
}
rl, err := readline.New("[no room]> ")
if err != nil {
panic(err)
}
defer rl.Close()
log := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = rl.Stdout()
w.TimeFormat = time.Stamp
})).With().Timestamp().Logger()
if !*debug {
log = log.Level(zerolog.InfoLevel)
}
client.Log = log
var lastRoomID id.RoomID
common.StartTime()
app := &Application{
Mc: client,
Log: log,
}
syncer := app.Mc.Syncer.(*mautrix.DefaultSyncer)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
lastRoomID = evt.RoomID
rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
// Filter out messages from the bot user account.
if evt.Sender.String() != fmt.Sprintf("@%s:%s", cfg.matrixUser, cfg.matrixHomeserver) {
app.Log.Info().
Str("sender", evt.Sender.String()).
Str("type", evt.Type.String()).
Str("id", evt.ID.String()).
Str("body", evt.Content.AsMessage().Body).
Msg("Received message")
if evt.Content.AsMessage().Body[:1] == "!" {
app.ParseCommand(evt)
return
} else {
app.ParseEvent(evt)
return
}
}
})
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
if evt.GetStateKey() == app.Mc.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := app.Mc.JoinRoomByID(evt.RoomID)
if err == nil {
lastRoomID = evt.RoomID
rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
app.Log.Info().
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Joined room after invite")
} else {
app.Log.Error().Err(err).
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Failed to join room after invite")
}
}
})
cryptoHelper, err := cryptohelper.NewCryptoHelper(app.Mc, []byte("meow"), cfg.database)
if err != nil {
panic(err)
}
cryptoHelper.LoginAs = &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: cfg.matrixUser},
Password: cfg.matrixPass,
}
err = cryptoHelper.Init()
if err != nil {
panic(err)
}
app.Mc.Crypto = cryptoHelper
app.Log.Info().Msg("Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
go func() {
err = app.Mc.SyncWithContext(syncCtx)
defer syncStopWait.Done()
if err != nil && !errors.Is(err, context.Canceled) {
panic(err)
}
}()
for {
line, err := rl.Readline()
if err != nil {
break
}
if lastRoomID == "" {
app.Log.Error().Msg("Wait for an incoming message before sending messages")
continue
}
resp, err := app.Mc.SendText(lastRoomID, line)
if err != nil {
app.Log.Error().Err(err).Msg("Failed to send event")
} else {
app.Log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
}
}
cancelSync()
syncStopWait.Wait()
err = cryptoHelper.Close()
if err != nil {
app.Log.Error().Err(err).Msg("Error closing database")
}
}

24
go.mod
View file

@ -4,24 +4,22 @@ go 1.20
require (
github.com/chzyer/readline v1.5.1
github.com/joho/godotenv v1.5.1
github.com/mattn/go-sqlite3 v1.14.16
github.com/rs/zerolog v1.29.1
maunium.net/go/mautrix v0.15.1
github.com/mattn/go-sqlite3 v1.14.18
github.com/rs/zerolog v1.31.0
maunium.net/go/mautrix v0.16.3-0.20231215142331-753cdb2e1cb0
)
require (
github.com/briandowns/openweathermap v0.19.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/tidwall/gjson v1.17.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
golang.org/x/crypto v0.8.0 // indirect
golang.org/x/net v0.9.0 // indirect
golang.org/x/sys v0.7.0 // indirect
go.mau.fi/util v0.2.1 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
maunium.net/go/maulogger/v2 v2.4.1 // indirect
)

56
go.sum
View file

@ -1,6 +1,4 @@
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/briandowns/openweathermap v0.19.0 h1:nkopLMEtZLxbZI1th6dOG6xkajpszofqf53r5K8mT9k=
github.com/briandowns/openweathermap v0.19.0/go.mod h1:0GLnknqicWxXnGi1IqoOaZIw+kIe5hkt+YM5WY3j8+0=
github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI=
@ -9,51 +7,45 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.18 h1:JL0eqdCOq6DJVNPSvArO/bIV9/P7fbGrV00LZHc+5aI=
github.com/mattn/go-sqlite3 v1.14.18/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0 h1:cgqwZtnR+IQfUYDLJ3Kiy4aE+O/wExTzEIg8xwC4Qfs=
github.com/shkh/lastfm-go v0.0.0-20191215035245-89a801c244e0/go.mod h1:n3nudMl178cEvD44PaopxH9jhJaQzthSxUzLO5iKMy4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.31.0 h1:FcTR3NnLWW+NnTwwhFWiJSZr4ECLpqCm6QsEnyvbV4A=
github.com/rs/zerolog v1.31.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM=
github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
go.mau.fi/util v0.2.1 h1:eazulhFE/UmjOFtPrGg6zkF5YfAyiDzQb8ihLMbsPWw=
go.mau.fi/util v0.2.1/go.mod h1:MjlzCQEMzJ+G8RsPawHzpLB8rwTo3aPIjG5FzBvQT/c=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa h1:FRnLl4eNAQl8hwxVVC17teOw8kdjVDVAiFMtgUdTSRQ=
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE=
golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.15.1 h1:pmCtMjYRpd83+2UL+KTRFYQo5to0373yulimvLK+1k0=
maunium.net/go/mautrix v0.15.1/go.mod h1:icQIrvz2NldkRLTuzSGzmaeuMUmw+fzO7UVycPeauN8=
maunium.net/go/mautrix v0.16.3-0.20231215142331-753cdb2e1cb0 h1:2ZWtBcTScQfMwpcoGeY4mLYXC6OmYN/4Qh2yhBiVNV4=
maunium.net/go/mautrix v0.16.3-0.20231215142331-753cdb2e1cb0/go.mod h1:YL4l4rZB46/vj/ifRMEjcibbvHjgxHftOF1SgmruLu4=

View file

@ -1,8 +0,0 @@
package commands
import "errors"
var (
ErrInternalServerError = errors.New("internal server error")
ErrWeatherLocationNotFound = errors.New("location not found")
)

View file

@ -1,30 +0,0 @@
package commands
import (
"fmt"
"io"
"net/http"
)
func Currency(currAmount, currFrom, currTo string) (string, error) {
basePath := "https://decapi.me/misc/currency/"
from := fmt.Sprintf("?from=%s", currFrom)
to := fmt.Sprintf("&to=%s", currTo)
value := fmt.Sprintf("&value=%s", currAmount)
// https://decapi.me/misc/currency/?from=usd&to=usd&value=10
resp, err := http.Get(fmt.Sprint(basePath + from + to + value))
if err != nil {
return "", ErrInternalServerError
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", ErrInternalServerError
}
reply := string(body)
return reply, nil
}

View file

@ -1,25 +0,0 @@
package commands
func Help(cn string) (string, error) {
switch cn {
case "phonetic":
if resp, err := phonetic(); err != nil {
return "", ErrInternalServerError
} else {
return resp, nil
}
}
return "this shouldnt happen xD", nil
}
func phonetic() (string, error) {
// This might look like complete ass depending on the
// matrix clients font. Looks fine on my Element client.
help := `
| Ё | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 | - | Ъ |
| Я | Ш | Е | Р | Т | Ы | У | И | О | П | Ю | Щ | Э |
| А | С | Д | Ф | Г | Ч | Й | К | Л | Ь | Ж |
| З | Х | Ц | В | Б | Н | М | ; | : |
`
return help, nil
}

View file

@ -1,33 +0,0 @@
package commands
import (
"fmt"
"os"
"github.com/joho/godotenv"
"github.com/shkh/lastfm-go/lastfm"
)
func LastFmUserRecent(user string) (string, error) {
err := godotenv.Load()
if err != nil {
return "", ErrInternalServerError
}
apiKey := os.Getenv("LAST_FM_API_KEY")
apiSecret := os.Getenv("LAST_FM_SECRET")
api := lastfm.New(apiKey, apiSecret)
result, _ := api.User.GetRecentTracks(lastfm.P{"user": user}) //discarding error
var reply string
for i, track := range result.Tracks {
// The 0th result is the most recent one since it goes from most recent
// to least recent.
if i == 0 {
reply = fmt.Sprintf("Most recently played track for user %v: %v - %v", user, track.Artist.Name, track.Name)
return reply, nil
}
}
return "", ErrInternalServerError
}

View file

@ -1,89 +0,0 @@
package commands
import "fmt"
var cm = map[string]string{
"`": "ё",
"~": "Ё",
"=": "ъ",
"+": "Ъ",
"[": "ю",
"]": "щ",
`\`: "э",
"{": "Ю",
"}": "Щ",
"|": "Э",
";": "ь",
":": "Ь",
"'": "ж",
`"`: "Ж",
"q": "я",
"w": "ш",
"e": "е",
"r": "р",
"t": "т",
"y": "ы",
"u": "у",
"i": "и",
"o": "о",
"p": "п",
"a": "а",
"s": "с",
"d": "д",
"f": "ф",
"g": "г",
"h": "ч",
"j": "й",
"k": "к",
"l": "л",
"z": "з",
"x": "х",
"c": "ц",
"v": "в",
"b": "б",
"n": "н",
"m": "м",
"Q": "Я",
"W": "Ш",
"E": "Е",
"R": "Р",
"T": "Т",
"Y": "Ы",
"U": "У",
"I": "И",
"O": "О",
"P": "П",
"A": "А",
"S": "С",
"D": "Д",
"F": "Ф",
"G": "Г",
"H": "Ч",
"J": "Й",
"K": "К",
"L": "Л",
"Z": "З",
"X": "Х",
"C": "Ц",
"V": "В",
"B": "Б",
"N": "Н",
"M": "М",
}
func Phonetic(message string) (string, error) {
var ts string
for _, c := range message {
if _, ok := cm[string(c)]; ok {
ts = ts + cm[string(c)]
} else {
ts = ts + string(c)
}
}
return fmt.Sprint(ts), nil
}

View file

@ -1,14 +0,0 @@
package commands
import (
"fmt"
"github.com/lyx0/nourybot-matrix/internal/common"
)
func Ping() (string, error) {
n := common.GetCommandsUsed()
up := common.GetUptime()
resp := fmt.Sprintf("Pong! Commands used: %v Last restart: %v", n, up)
return resp, nil
}

View file

@ -1,47 +0,0 @@
package commands
import (
"fmt"
"os"
owm "github.com/briandowns/openweathermap"
"github.com/joho/godotenv"
)
// Weather queries the OpenWeatherMap Api for the given location and sends the
// current weather response to the target twitch chat.
func Weather(location string) (string, error) {
err := godotenv.Load()
if err != nil {
return "", ErrInternalServerError
}
owmKey := os.Getenv("OWM_KEY")
w, err := owm.NewCurrent("C", "en", owmKey)
if err != nil {
return "", ErrInternalServerError
}
if err := w.CurrentByName(location); err != nil {
return "", ErrInternalServerError
}
// Longitude and Latitude are returned as 0 when the supplied location couldn't be
// assigned to a OpenWeatherMap location.
if w.GeoPos.Longitude == 0 && w.GeoPos.Latitude == 0 {
return "", ErrWeatherLocationNotFound
} else {
// Weather for Vilnius, LT: Feels like: 29.67°C. Currently 29.49°C with a high of 29.84°C and a low of 29.49°C, humidity: 45%, wind: 6.17m/s.
reply := fmt.Sprintf("Weather for %s, %s: Feels like: %v°C. Currently %v°C with a high of %v°C and a low of %v°C, humidity: %v%%, wind: %vm/s.",
w.Name,
w.Sys.Country,
w.Main.FeelsLike,
w.Main.Temp,
w.Main.TempMax,
w.Main.TempMin,
w.Main.Humidity,
w.Wind.Speed,
)
return reply, nil
}
}

View file

@ -1,55 +0,0 @@
package commands
import (
"encoding/json"
"fmt"
"github.com/lyx0/nourybot-matrix/internal/common"
"io"
"net/http"
)
type xkcdResponse struct {
Num int `json:"num"`
SafeTitle string `json:"safe_title"`
Img string `json:"img"`
}
func Xkcd() (string, error) {
response, err := http.Get("https://xkcd.com/info.0.json")
if err != nil {
return "", ErrInternalServerError
}
responseData, err := io.ReadAll(response.Body)
if err != nil {
return "", ErrInternalServerError
}
var responseObject xkcdResponse
if err = json.Unmarshal(responseData, &responseObject); err != nil {
return "", ErrInternalServerError
}
reply := fmt.Sprint("Current Xkcd #", responseObject.Num, " Title: ", responseObject.SafeTitle, " ", responseObject.Img)
return reply, nil
}
func RandomXkcd() (string, error) {
comicNum := fmt.Sprint(common.GenerateRandomNumber(2772))
response, err := http.Get(fmt.Sprint("http://xkcd.com/" + comicNum + "/info.0.json"))
if err != nil {
return "", ErrInternalServerError
}
responseData, err := io.ReadAll(response.Body)
if err != nil {
return "", ErrInternalServerError
}
var responseObject xkcdResponse
if err = json.Unmarshal(responseData, &responseObject); err != nil {
return "", ErrInternalServerError
}
reply := fmt.Sprint("Random Xkcd #", responseObject.Num, " Title: ", responseObject.SafeTitle, " ", responseObject.Img)
return reply, nil
}

View file

@ -1,16 +0,0 @@
package common
var (
tempCommands = 0
)
// CommandUsed is called on every command incremenenting tempCommands.
func CommandUsed() {
tempCommands++
}
// GetCommandsUsed returns the amount of commands that have been used
// since the last restart.
func GetCommandsUsed() int {
return tempCommands
}

View file

@ -1,35 +0,0 @@
package common
import (
"fmt"
"math/rand"
"strconv"
)
// StrGenerateRandomNumber generates a random number from
// a given max value as a string
func StrGenerateRandomNumber(max string) int {
num, err := strconv.Atoi(max)
if num < 1 {
return 0
}
if err != nil {
fmt.Printf("Supplied value %v is not a number", num)
return 0
} else {
return rand.Intn(num)
}
}
// GenerateRandomNumber returns a random number from
// a given max value as a int
func GenerateRandomNumber(max int) int {
return rand.Intn(max)
}
// GenerateRandomNumberRange returns a random number
// over a given minimum and maximum range.
func GenerateRandomNumberRange(min int, max int) int {
return (rand.Intn(max-min) + min)
}

View file

@ -1,19 +0,0 @@
package common
import (
"github.com/dustin/go-humanize"
"time"
)
var (
uptime time.Time
)
func StartTime() {
uptime = time.Now()
}
func GetUptime() string {
h := humanize.Time(uptime)
return h
}

153
main.go Normal file
View file

@ -0,0 +1,153 @@
// Copyright (C) 2017 Tulir Asokan
// Copyright (C) 2018-2020 Luca Weiss
// Copyright (C) 2023 Tulir Asokan
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"sync"
"time"
"github.com/chzyer/readline"
_ "github.com/mattn/go-sqlite3"
"github.com/rs/zerolog"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/crypto/cryptohelper"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
var homeserver = flag.String("homeserver", "", "Matrix homeserver")
var username = flag.String("username", "", "Matrix username localpart")
var password = flag.String("password", "", "Matrix password")
var database = flag.String("database", "mautrix-example.db", "SQLite database path")
var debug = flag.Bool("debug", false, "Enable debug logs")
func main() {
flag.Parse()
if *username == "" || *password == "" || *homeserver == "" {
_, _ = fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0])
flag.PrintDefaults()
os.Exit(1)
}
client, err := mautrix.NewClient(*homeserver, "", "")
if err != nil {
panic(err)
}
rl, err := readline.New("[no room]> ")
if err != nil {
panic(err)
}
defer rl.Close()
log := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
w.Out = rl.Stdout()
w.TimeFormat = time.Stamp
})).With().Timestamp().Logger()
if !*debug {
log = log.Level(zerolog.InfoLevel)
}
client.Log = log
var lastRoomID id.RoomID
syncer := client.Syncer.(*mautrix.DefaultSyncer)
syncer.OnEventType(event.EventMessage, func(source mautrix.EventSource, evt *event.Event) {
lastRoomID = evt.RoomID
rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
log.Info().
Str("sender", evt.Sender.String()).
Str("type", evt.Type.String()).
Str("id", evt.ID.String()).
Str("body", evt.Content.AsMessage().Body).
Msg("Received message")
})
syncer.OnEventType(event.StateMember, func(source mautrix.EventSource, evt *event.Event) {
if evt.GetStateKey() == client.UserID.String() && evt.Content.AsMember().Membership == event.MembershipInvite {
_, err := client.JoinRoomByID(context.TODO(), evt.RoomID)
if err == nil {
lastRoomID = evt.RoomID
rl.SetPrompt(fmt.Sprintf("%s> ", lastRoomID))
log.Info().
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Joined room after invite")
} else {
log.Error().Err(err).
Str("room_id", evt.RoomID.String()).
Str("inviter", evt.Sender.String()).
Msg("Failed to join room after invite")
}
}
})
cryptoHelper, err := cryptohelper.NewCryptoHelper(client, []byte("meow"), *database)
if err != nil {
panic(err)
}
// You can also store the user/device IDs and access token and put them in the client beforehand instead of using LoginAs.
//client.UserID = "..."
//client.DeviceID = "..."
//client.AccessToken = "..."
// You don't need to set a device ID in LoginAs because the crypto helper will set it for you if necessary.
cryptoHelper.LoginAs = &mautrix.ReqLogin{
Type: mautrix.AuthTypePassword,
Identifier: mautrix.UserIdentifier{Type: mautrix.IdentifierTypeUser, User: *username},
Password: *password,
}
// If you want to use multiple clients with the same DB, you should set a distinct database account ID for each one.
//cryptoHelper.DBAccountID = ""
err = cryptoHelper.Init()
if err != nil {
panic(err)
}
// Set the client crypto helper in order to automatically encrypt outgoing messages
client.Crypto = cryptoHelper
log.Info().Msg("Now running")
syncCtx, cancelSync := context.WithCancel(context.Background())
var syncStopWait sync.WaitGroup
syncStopWait.Add(1)
go func() {
err = client.SyncWithContext(syncCtx)
defer syncStopWait.Done()
if err != nil && !errors.Is(err, context.Canceled) {
panic(err)
}
}()
for {
line, err := rl.Readline()
if err != nil { // io.EOF
break
}
if lastRoomID == "" {
log.Error().Msg("Wait for an incoming message before sending messages")
continue
}
resp, err := client.SendText(context.TODO(), lastRoomID, line)
if err != nil {
log.Error().Err(err).Msg("Failed to send event")
} else {
log.Info().Str("event_id", resp.EventID.String()).Msg("Event sent")
}
}
cancelSync()
syncStopWait.Wait()
err = cryptoHelper.Close()
if err != nil {
log.Error().Err(err).Msg("Error closing database")
}
}