package main import ( "crypto/tls" "fmt" "log" "net/http" "os" "path/filepath" "strings" "time" "github.com/labstack/echo/v5" "github.com/lrstanley/girc" "github.com/pelletier/go-toml/v2" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/plugins/ghupdate" "github.com/pocketbase/pocketbase/plugins/jsvm" "github.com/pocketbase/pocketbase/plugins/migratecmd" ) const ( reconnectTime = 30 ) type handlerWrapper struct { irc *girc.Client config TomlConfig app *pocketbase.PocketBase } type SMS struct { From string `json:"from"` Text string `json:"text"` SentStamp int64 `json:"sentStamp"` ReceivedStamp int64 `json:"receivedStamp"` Sim string `json:"sim"` } type TomlConfig struct { IrcServer string IrcPort int IrcNick string IrcSaslUser string IrcSaslPass string IrcChannel string } // curl -X 'POST' 'http://127.0.0.1:8090/sms' -H 'content-type: application/json; charset=utf-8' -d $'{"from":"1234567890","text":"Test"}' func (hw handlerWrapper) postHandler(context echo.Context) error { user, pass, ok := context.Request().BasicAuth() if !ok { return context.JSON(http.StatusUnauthorized, "unauthorized") } userRecord, err := hw.app.Dao().FindAuthRecordByUsername("users", user) if err != nil { return context.JSON(http.StatusUnauthorized, "unauthorized") } if !userRecord.ValidatePassword(pass) { return context.JSON(http.StatusUnauthorized, "unauthorized") } sms := new(SMS) if err := context.Bind(sms); err != nil { return context.String(http.StatusBadRequest, "bad request") } smsInfoReal := SMS{ From: sms.From, Text: sms.Text, SentStamp: sms.SentStamp, ReceivedStamp: sms.ReceivedStamp, Sim: sms.Sim, } for { if hw.irc != nil { if hw.irc.IsConnected() { break } } } hw.irc.Cmd.Message(hw.config.IrcChannel, fmt.Sprintf("From: %s, Text: %s", sms.From, sms.Text)) log.Println(smsInfoReal) return context.JSON(http.StatusOK, "OK") } func runIRC(appConfig TomlConfig, ircChan chan *girc.Client) { irc := girc.New(girc.Config{ Server: appConfig.IrcServer, Port: appConfig.IrcPort, Nick: appConfig.IrcNick, User: appConfig.IrcNick, Name: appConfig.IrcNick, SSL: true, TLSConfig: &tls.Config{InsecureSkipVerify: true}, }) saslUser := appConfig.IrcSaslUser saslPass := appConfig.IrcSaslPass if saslUser != "" && saslPass != "" { irc.Config.SASL = &girc.SASLPlain{ User: appConfig.IrcSaslUser, Pass: appConfig.IrcSaslPass, } } irc.Handlers.AddBg(girc.CONNECTED, func(c *girc.Client, e girc.Event) { c.Cmd.Join(appConfig.IrcChannel) }) // irc.Handlers.AddBg(girc.PRIVMSG, func(c *girc.Client, e girc.Event) {}) ircChan <- irc for { if err := irc.Connect(); err != nil { log.Println(err) log.Println("reconnecting in 30 seconds") time.Sleep(reconnectTime * time.Second) } else { return } } } func defaultPublicDir() string { if strings.HasPrefix(os.Args[0], os.TempDir()) { return "./pb_public" } return filepath.Join(os.Args[0], "../pb_public") } func main() { var appConfig TomlConfig data, err := os.ReadFile("/opt/smswebhook/config.toml") if err != nil { log.Fatal(err) } err = toml.Unmarshal(data, &appConfig) if err != nil { log.Fatal(err) } log.Println(appConfig) app := pocketbase.New() ircChan := make(chan *girc.Client, 1) hw := handlerWrapper{irc: nil, config: appConfig, app: app} app.OnBeforeServe().Add(func(e *core.ServeEvent) error { go runIRC(appConfig, ircChan) hw.irc = <-ircChan e.Router.POST("/sms", hw.postHandler) return nil }) var hooksDir string app.RootCmd.PersistentFlags().StringVar( &hooksDir, "hooksDir", "", "the directory with the JS app hooks", ) var hooksWatch bool app.RootCmd.PersistentFlags().BoolVar( &hooksWatch, "hooksWatch", true, "auto restart the app on pb_hooks file change", ) var hooksPool int app.RootCmd.PersistentFlags().IntVar( &hooksPool, "hooksPool", 25, "the total prewarm goja.Runtime instances for the JS app hooks execution", ) var migrationsDir string app.RootCmd.PersistentFlags().StringVar( &migrationsDir, "migrationsDir", "", "the directory with the user defined migrations", ) var automigrate bool app.RootCmd.PersistentFlags().BoolVar( &automigrate, "automigrate", true, "enable/disable auto migrations", ) var publicDir string app.RootCmd.PersistentFlags().StringVar( &publicDir, "publicDir", defaultPublicDir(), "the directory to serve static files", ) var indexFallback bool app.RootCmd.PersistentFlags().BoolVar( &indexFallback, "indexFallback", true, "fallback the request to index.html on missing static path (eg. when pretty urls are used with SPA)", ) var queryTimeout int app.RootCmd.PersistentFlags().IntVar( &queryTimeout, "queryTimeout", 30, "the default SELECT queries timeout in seconds", ) app.RootCmd.ParseFlags(os.Args[1:]) jsvm.MustRegister(app, jsvm.Config{ MigrationsDir: migrationsDir, HooksDir: hooksDir, HooksWatch: hooksWatch, HooksPoolSize: hooksPool, }) migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{ TemplateLang: migratecmd.TemplateLangJS, Automigrate: automigrate, Dir: migrationsDir, }) ghupdate.MustRegister(app, app.RootCmd, ghupdate.Config{}) app.OnAfterBootstrap().PreAdd(func(e *core.BootstrapEvent) error { app.Dao().ModelQueryTimeout = time.Duration(queryTimeout) * time.Second return nil }) app.OnBeforeServe().Add(func(e *core.ServeEvent) error { e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(publicDir), indexFallback)) return nil }) if err := app.Start(); err != nil { log.Fatal(err) } }