telegram messaging

main
alex 2023-10-20 21:44:22 +02:00
parent 90bc515284
commit 19557e0bea
31 changed files with 1199 additions and 6 deletions

18
go.mod
View File

@ -3,27 +3,37 @@ module jannex/telegram-bot-manager
go 1.21.0
require (
git.ex.umbach.dev/Alex/roese-utils v1.0.16
git.ex.umbach.dev/Alex/roese-utils v1.0.17
github.com/gofiber/fiber/v2 v2.50.0
github.com/joho/godotenv v1.5.1
gorm.io/driver/mysql v1.5.2
gorm.io/gorm v1.25.5
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.15.5 // indirect
github.com/go-sql-driver/mysql v1.7.0 // indirect
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rs/zerolog v1.31.0 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.50.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/sys v0.13.0 // indirect
gorm.io/driver/mysql v1.5.2 // indirect
gorm.io/gorm v1.25.5 // indirect
golang.org/x/text v0.8.0 // indirect
)

40
go.sum
View File

@ -1,10 +1,27 @@
git.ex.umbach.dev/Alex/roese-utils v1.0.16 h1:B9yVx5nL7FfUPJmqHDs58mq8MB2wSox1fK34EP3dqhc=
git.ex.umbach.dev/Alex/roese-utils v1.0.16/go.mod h1:mov3ZaoSu+GBQU1uXN/AfSK6MF3/3WxUk38Tb6IKxbI=
git.ex.umbach.dev/Alex/roese-utils v1.0.17 h1:uXq9/1sgIfu6qbyPPtgU0y0AbNFcS6IxXuNBcltEk/M=
git.ex.umbach.dev/Alex/roese-utils v1.0.17/go.mod h1:mov3ZaoSu+GBQU1uXN/AfSK6MF3/3WxUk38Tb6IKxbI=
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU=
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofiber/fiber/v2 v2.50.0 h1:ia0JaB+uw3GpNSCR5nvC5dsaxXjRU5OEu36aytx+zGw=
github.com/gofiber/fiber/v2 v2.50.0/go.mod h1:21eytvay9Is7S6z+OgPi7c7n4++tnClWmhpimVHMimw=
@ -18,6 +35,8 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
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.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@ -26,22 +45,43 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.50.0 h1:H7fweIlBm0rXLs2q0XbalvJ6r0CUPFWK3/bB4N13e9M=
github.com/valyala/fasthttp v1.50.0/go.mod h1:k2zXd82h/7UZc3VOdJ2WaUqt1uZ/XpXAfE9i+HBC3lA=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
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.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
gorm.io/driver/mysql v1.5.2/go.mod h1:pQLhh1Ut/WUAySdTHwBpBv6+JKcj+ua4ZFx1QQTBzb8=
gorm.io/gorm v1.25.2-0.20230530020048-26663ab9bf55/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=

View File

@ -4,6 +4,7 @@ import (
"fmt"
"jannex/telegram-bot-manager/modules/config"
"jannex/telegram-bot-manager/modules/database"
"jannex/telegram-bot-manager/modules/telegram"
"jannex/telegram-bot-manager/modules/utils"
"jannex/telegram-bot-manager/routers/router"
"os"
@ -58,6 +59,8 @@ func main() {
router.SetupRoutes(app)
telegram.InitBot()
tblogger.AddSystemLog(rslogger.LogTypeInfo, "Server started")
app.Listen(config.Cfg.Host + ":" + config.Cfg.Port)

66
modules/cache/tempverifycodes.go vendored Normal file
View File

@ -0,0 +1,66 @@
package cache
import (
"jannex/telegram-bot-manager/modules/structs"
"sync"
)
var tempVerifyCodes = make(map[string]structs.TempVerifyCode)
var tMu sync.RWMutex
func AddTempVerifyCode(tempVerifyCode structs.TempVerifyCode) {
tMu.Lock()
defer tMu.Unlock()
tempVerifyCodes[tempVerifyCode.UserId] = tempVerifyCode
}
func GetTempVerifyByCode(code string) (userId string, found bool) {
tMu.RLock()
defer tMu.RUnlock()
for _, v := range tempVerifyCodes {
if v.Code == code {
return v.UserId, true
}
}
return "", false
}
func GetTempVerifyCode(userId string) (string, bool) {
tMu.RLock()
defer tMu.RUnlock()
if v, ok := tempVerifyCodes[userId]; ok {
return v.Code, true
}
return "", false
}
func RemoveTempVerifyCode(userId string) {
tMu.Lock()
defer tMu.Unlock()
delete(tempVerifyCodes, userId)
}
func GetTempVerifyCodes() []structs.TempVerifyCode {
tMu.RLock()
defer tMu.RUnlock()
var tempVerifyCodesSlice []structs.TempVerifyCode
for _, v := range tempVerifyCodes {
tempVerifyCodesSlice = append(tempVerifyCodesSlice, v)
}
return tempVerifyCodesSlice
}
func RemoveTempVerifyCodes() {
tMu.Lock()
defer tMu.Unlock()
}

View File

@ -3,6 +3,7 @@ package database
import (
"fmt"
"jannex/telegram-bot-manager/modules/config"
"jannex/telegram-bot-manager/modules/structs"
"gorm.io/driver/mysql"
"gorm.io/gorm"
@ -34,4 +35,5 @@ func InitDatabase() {
DB = db
db.AutoMigrate(&structs.VerifiedUser{})
}

View File

@ -0,0 +1,7 @@
package structs
type NotificationBody struct {
UsersIds []string
Type uint8
Message string
}

View File

@ -0,0 +1,24 @@
package structs
import "time"
type VerifiedUser struct {
UserId string
ChatId int
CreatedAt time.Time
}
type VerifyCodeParams struct {
UserId string
}
// swagger:response VerifyCodeResponse
type VerifyCodeResponse struct {
Code string
}
type TempVerifyCode struct {
UserId string
Code string
ExpiredAt time.Time
}

View File

@ -0,0 +1,155 @@
package telegram
import (
"jannex/telegram-bot-manager/modules/cache"
"jannex/telegram-bot-manager/modules/database"
"jannex/telegram-bot-manager/modules/logger"
"jannex/telegram-bot-manager/modules/structs"
"jannex/telegram-bot-manager/modules/utils"
"regexp"
"time"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
"github.com/rs/zerolog/log"
)
var bot *tgbotapi.BotAPI
func InitBot() {
var err error
bot, err = tgbotapi.NewBotAPI("6450280279:AAGxXdPv_YwRtl_pXIqYojqOecBrEjn3fJo")
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to initialize Telegram bot, err: %v", err.Error())
log.Fatal().Err(err).Msgf("Failed to initialize Telegram bot, err: %v", err.Error())
}
//bot.Debug = false
logger.AddSystemLog(rslogger.LogTypeInfo, "Bot connected as %s", bot.Self.UserName)
go IncomingMessagesHandler()
}
func IncomingMessagesHandler() {
updateConfig := tgbotapi.NewUpdate(0)
updateConfig.Timeout = 60
updates, err := bot.GetUpdatesChan(updateConfig)
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to get updates from Telegram bot, err: %v", err.Error())
log.Fatal().Err(err).Msgf("Failed to get updates from Telegram bot, err: %v", err.Error())
}
for update := range updates {
if update.Message == nil {
continue
}
chatID := update.Message.Chat.ID
text := update.Message.Text
if text == "" {
continue
}
regVerify := regexp.MustCompile(`/verify (\w+)`)
regVerifyMatches := regVerify.FindStringSubmatch(text)
regUnsubscribe := regexp.MustCompile(`/unsubscribe`)
var replyMessage string
if len(regVerifyMatches) == 2 {
code := regVerifyMatches[1]
if len(code) != utils.VerifyCodeLength {
logger.AddSystemLog(rslogger.LogTypeWarning, "Received code with wrong length from user: %s %s. Message: %s", update.Message.From.FirstName, update.Message.From.LastName, code)
continue
}
var userId string
var ok bool
if userId, ok = cache.GetTempVerifyByCode(code); !ok {
// code not found in cache
logger.AddSystemLog(rslogger.LogTypeWarning, "Received code which was not found in cache from user: %s %s. Message: %s", update.Message.From.FirstName, update.Message.From.LastName, code)
replyMessage = "The code you entered is invalid. Please check the code and try again."
} else {
// code found in cache
cache.RemoveTempVerifyCode(userId)
var foundVerifiedUser structs.VerifiedUser
res := database.DB.Where("chat_id = ?", update.Message.From.ID).First(&foundVerifiedUser)
if res.Error != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to get verified user from database, err: %v", res.Error.Error())
continue
}
if foundVerifiedUser.UserId != "" {
// user already verified
replyMessage = "You have already verified your account. You will continue to receive notifications. Type /unsubscribe to stop receiving notifications."
logger.AddSystemLog(rslogger.LogTypeWarning, "User: %s %s has tried to verify his account with code %s, but has already verified his account", update.Message.From.FirstName, update.Message.From.LastName, code)
} else {
// user not verified
replyMessage = "You have successfully verified your account. You will now receive notifications. Type /unsubscribe to stop receiving notifications."
database.DB.Create(&structs.VerifiedUser{
UserId: userId,
ChatId: update.Message.From.ID,
CreatedAt: time.Now(),
})
logger.AddSystemLog(rslogger.LogTypeInfo, "User: %s %s has subscribed to receive notifications", update.Message.From.FirstName, update.Message.From.LastName)
}
}
// reply to user
reply := tgbotapi.NewMessage(chatID, replyMessage)
_, err := bot.Send(reply)
if err != nil {
log.Error().Msgf("Failed to send subscribed reply to user, err: %v", err.Error())
logger.AddSystemLog(rslogger.LogTypeError, "Failed to send reply to user, err: %v", err.Error())
}
} else if regUnsubscribe.MatchString(text) {
res := database.DB.Delete(&structs.VerifiedUser{}, "chat_id = ?", update.Message.From.ID)
if res.Error != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to delete verified user from database, err: %v", res.Error.Error())
continue
}
if res.RowsAffected == 0 {
logger.AddSystemLog(rslogger.LogTypeWarning, "User: %s %s has tried to unsubscribe from receiving notifications, but has not been verified", update.Message.From.FirstName, update.Message.From.LastName)
replyMessage = "You have not been verified yet. Type /verify <code> to start receiving notifications. The code can be found in the user profile on the dashboard."
} else {
logger.AddSystemLog(rslogger.LogTypeInfo, "User: %s %s has unsubscribed from receiving notifications", update.Message.From.FirstName, update.Message.From.LastName)
replyMessage = "You have unsubscribed from receiving notifications. Type /verify <code> to start receiving notifications again. The code can be found in the user profile on the dashboard."
}
reply := tgbotapi.NewMessage(chatID, replyMessage)
_, err := bot.Send(reply)
if err != nil {
logger.AddSystemLog(rslogger.LogTypeError, "Failed to send reply to user, err: %v", err.Error())
log.Error().Msgf("Failed to send unsubscribed reply to user, err: %v", err.Error())
}
} else {
logger.AddSystemLog(rslogger.LogTypeWarning, "Received unknown message: %s from user: %s %s", text, update.Message.From.FirstName, update.Message.From.LastName)
}
}
}

View File

@ -1 +1,8 @@
package utils
import "time"
const (
VerifyCodeLength = 6
TempVerifyCodeExpirationTime = 5 * time.Minute
)

BIN
public/swagger/favicon-16x16.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

BIN
public/swagger/favicon-32x32.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

59
public/swagger/index.css Executable file
View File

@ -0,0 +1,59 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}
/* Position the navbar container inside the image */
.container {
position: absolute;
width: auto;
font-family: Arial, Helvetica, sans-serif;
margin: 65px 5px;
}
#container-versions {
margin: 110px 5px;
}
/* The navbar */
.topnav {
overflow: hidden;
background-color: #333;
border-radius: 10px;
}
.topnav:first-child {
margin-bottom: 10px;
}
/* Navbar links */
.topnav a {
float: left;
color: #f2f2f2;
text-align: center;
padding: 10px 12px;
text-decoration: none;
font-size: 14px;
}
.topnav a:hover {
opacity: 0.7;
border-bottom: 5px solid #62a03f;
}
.topnav a.active {
color: #999494;
border-bottom: 5px solid #62a03f;
}

19
public/swagger/index.html Executable file
View File

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,11 @@
window.onload = function() {
window.ui = SwaggerUIBundle({
url: 'swagger.json',
dom_id: '#swagger-ui',
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
layout: "StandaloneLayout",
});
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3
public/swagger/swagger-ui.css Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

2
public/swagger/swagger-ui.js Executable file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

600
public/swagger/swagger.json Normal file
View File

@ -0,0 +1,600 @@
{
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"schemes": [
"https"
],
"swagger": "2.0",
"info": {
"title": "JNX Robot Control Manager API Documentation.",
"version": "1.0.0"
},
"host": "jannex",
"basePath": "/v1",
"paths": {
"/api/v1/robots": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robots"
],
"summary": "Get all robots",
"operationId": "getRobots",
"parameters": [
{
"description": "Page number",
"name": "page",
"in": "query"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/RobotsResponse"
}
}
}
}
},
"/api/v1/urobots": {
"get": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robots"
],
"summary": "Get all unauthorized robots",
"operationId": "getUnauthorizedRobots",
"parameters": [
{
"description": "Page number",
"name": "page",
"in": "query"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/UnauthorizedRobotsResponse"
}
}
}
}
},
"/control/1": {
"post": {
"description": "This is used to control Rex.\n",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"control"
],
"summary": "Control Rex.",
"operationId": "controlRex",
"parameters": [
{
"description": "Control Rex body.",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/ControlBody"
}
}
],
"responses": {
"200": {
"description": "Control Rex"
},
"400": {
"description": "Invalid request body"
},
"422": {
"description": "Robot not found"
}
}
}
},
"/control/1/finish": {
"post": {
"description": "This is used to finish control Rex.\n",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"control"
],
"summary": "Finish control Rex.",
"operationId": "finishControlRex",
"responses": {
"200": {
"description": "Finish control Rex"
},
"400": {
"description": "Invalid robot name"
},
"422": {
"description": "Robot not found"
}
}
}
},
"/permitjoin": {
"get": {
"description": "This is used to get the current permit join status.\n",
"produces": [
"application/json"
],
"tags": [
"permitjoin"
],
"summary": "Get permit join.",
"operationId": "getPermitJoin",
"responses": {
"200": {
"description": "Permit join status",
"schema": {
"$ref": "#/definitions/PermitJoinResponse"
}
}
}
}
},
"/permitjoin/{enabled}": {
"post": {
"description": "This is used to enable or disable permit join.\n",
"tags": [
"permitjoin"
],
"summary": "Set permit join.",
"operationId": "setPermitJoin",
"parameters": [
{
"description": "Enable or disable permit join. 0 = disable, 1 = enable.",
"name": "enabled",
"in": "path",
"required": true,
"schema": {
"type": "integer",
"enum": [
0,
1
]
}
}
],
"responses": {
"200": {
"description": "Permit join set"
},
"400": {
"description": "Invalid request body"
}
}
}
},
"/robot": {
"post": {
"description": "This is the first request from the robot. It will be used to identify the robot.\n",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "First request from robot.",
"operationId": "robotFirstRequest",
"parameters": [
{
"description": "First request body.",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/FirstRequestBody"
}
}
],
"responses": {
"200": {
"description": "Robot identified",
"schema": {
"$ref": "#/definitions/StatusResponse"
}
},
"400": {
"description": "Invalid request body"
},
"403": {
"description": "Permit join is enabled"
}
}
},
"patch": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "Update robot.",
"operationId": "robotUpdate",
"parameters": [
{
"description": "Update robot body.",
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/Robot"
}
}
],
"responses": {
"200": {
"description": "Robot updated"
},
"400": {
"description": "Invalid request body"
},
"422": {
"description": "Robot not found"
}
}
}
},
"/robot/authorize/{robotId}": {
"post": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "Authorize robot.",
"operationId": "robotAuthorize",
"parameters": [
{
"description": "Robot id.",
"name": "robotId",
"in": "params",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Robot authorized"
},
"400": {
"description": "Invalid robot id"
},
"422": {
"description": "Robot not found"
}
}
}
},
"/robot/deny/{robotId}": {
"delete": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "Deny unauthorized robot.",
"operationId": "robotDenyUnauthorizedRobot",
"parameters": [
{
"description": "Robot id.",
"name": "robotId",
"in": "params",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Robot denied"
},
"400": {
"description": "Invalid robot id"
}
}
}
},
"/robot/fuj/{robotId}": {
"patch": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "Free up job from robot.",
"operationId": "robotFreeUpJob",
"parameters": [
{
"description": "Robot id.",
"name": "robotId",
"in": "params",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Job freed up"
},
"400": {
"description": "Invalid robot id"
}
}
}
},
"/robot/{robotId}": {
"delete": {
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"robot"
],
"summary": "Delete robot.",
"operationId": "robotDelete",
"parameters": [
{
"description": "Robot id.",
"name": "robotId",
"in": "params",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Robot deleted"
},
"400": {
"description": "Invalid robot id"
}
}
}
}
},
"definitions": {
"APIRobot": {
"type": "object",
"properties": {
"Address": {
"type": "string"
},
"ConnectedAt": {
"type": "string",
"format": "date-time"
},
"CreatedAt": {
"type": "string",
"format": "date-time"
},
"CurrentJobName": {
"type": "string"
},
"FirmwareVersion": {
"type": "string"
},
"Id": {
"type": "string"
},
"JobsWaitingCount": {
"type": "integer",
"format": "int64"
},
"JobsWaitingNameList": {
"type": "array",
"items": {
"type": "string"
}
},
"Name": {
"type": "string"
},
"Status": {
"type": "integer",
"format": "uint8"
},
"Type": {
"type": "integer",
"format": "uint8"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"ControlBody": {
"type": "object",
"properties": {
"JobId": {
"type": "string"
},
"JobName": {
"type": "string"
},
"RobotName": {
"type": "string"
},
"Task": {
"$ref": "#/definitions/ControlTask"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"ControlTask": {
"type": "object",
"properties": {
"ConnectedModule": {
"type": "integer",
"format": "int64"
},
"X": {
"type": "integer",
"format": "int64"
},
"Y": {
"type": "integer",
"format": "int64"
},
"Z": {
"type": "integer",
"format": "int64"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"FirstRequestBody": {
"type": "object",
"properties": {
"FirmwareVersion": {
"description": "used firmware version of the robot",
"type": "string"
},
"Id": {
"description": "robot id",
"type": "string"
},
"Type": {
"description": "robot type",
"type": "integer",
"format": "uint8"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"PermitJoinResponse": {
"type": "object",
"properties": {
"Enabled": {
"type": "boolean"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"RobotsResponse": {
"type": "object",
"properties": {
"Robots": {
"type": "array",
"items": {
"$ref": "#/definitions/APIRobot"
}
},
"TotalPages": {
"type": "integer",
"format": "int64"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"StatusResponse": {
"type": "object",
"properties": {
"Status": {
"type": "integer",
"format": "int64"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"UnauthorizedRobot": {
"type": "object",
"properties": {
"Address": {
"type": "string"
},
"ConnectedAt": {
"type": "string",
"format": "date-time"
},
"CreatedAt": {
"type": "string",
"format": "date-time"
},
"FirmwareVersion": {
"type": "string"
},
"Id": {
"type": "string"
},
"Type": {
"type": "integer",
"format": "uint8"
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
},
"UnauthorizedRobotsResponse": {
"type": "object",
"properties": {
"TotalPages": {
"type": "integer",
"format": "int64"
},
"UnauthorizedRobots": {
"type": "array",
"items": {
"$ref": "#/definitions/UnauthorizedRobot"
}
}
},
"x-go-package": "jannex/robot-control-manager/modules/structs"
}
}
}

View File

@ -0,0 +1,18 @@
package notification
import (
"jannex/telegram-bot-manager/modules/structs"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"github.com/gofiber/fiber/v2"
)
func SendNotification(c *fiber.Ctx) error {
var body structs.NotificationBody
if err := rsutils.BodyParserHelper(c, &body); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
return c.SendStatus(fiber.StatusOK)
}

View File

@ -0,0 +1,60 @@
package verifycode
import (
"jannex/telegram-bot-manager/modules/cache"
"jannex/telegram-bot-manager/modules/logger"
"jannex/telegram-bot-manager/modules/structs"
"jannex/telegram-bot-manager/modules/utils"
"time"
"git.ex.umbach.dev/Alex/roese-utils/rslogger"
"git.ex.umbach.dev/Alex/roese-utils/rsutils"
"github.com/gofiber/fiber/v2"
)
func GetVerifyCode(c *fiber.Ctx) error {
// swagger:operation GET /v1/verifycode/{userId} verifycode getVerifyCode
// ---
// summary: Get verify code for user
// consumes:
// - application/json
// produces:
// - application/json
// parameters:
// - name: userId
// in: params
// description: User ID
// required: true
// type: string
// responses:
// '200':
// description: OK
// schema:
// "$ref": "#/definitions/VerifyCodeResponse"
// '400':
// description: Bad request
// '500':
// description: Internal server error
var params structs.VerifyCodeParams
if err := rsutils.ParamsParserHelper(c, &params); err != nil {
return c.SendStatus(fiber.StatusBadRequest)
}
code, err := rsutils.GenerateCode(6)
if err != nil {
return c.SendStatus(fiber.StatusInternalServerError)
}
cache.AddTempVerifyCode(structs.TempVerifyCode{
UserId: params.UserId,
Code: code,
ExpiredAt: time.Now().Add(utils.TempVerifyCodeExpirationTime),
})
logger.AddSystemLog(rslogger.LogTypeInfo, "Generated verify code for user %s", params.UserId)
return c.JSON(structs.VerifyCodeResponse{Code: code})
}

View File

@ -1,8 +1,18 @@
package router
import "github.com/gofiber/fiber/v2"
import (
"jannex/telegram-bot-manager/routers/api/v1/verifycode"
"github.com/gofiber/fiber/v2"
)
func SetupRoutes(app *fiber.App) {
//v1 := app.Group("/v1")
v1 := app.Group("/v1")
vc := v1.Group("/verifycode")
vc.Get("/:userId", verifycode.GetVerifyCode)
n := v1.Group("/notification")
app.Static("/", "./public/")
}