mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 09:00:10 +00:00
almost complete telegram user verification
When signing up, the user is given a pin code which they send to a telegram bot. This provides user verification, but more importantly allows the bot to message the user, as the Telegram API requires the user to interact with the bot before it can do the opposite. The bot should recognize the correct language, but a /lang command is also provided to change it. The verification process is pretty much functional but ui is still broken, and it isn't properly integrated yet.
This commit is contained in:
parent
0e21942cd6
commit
99875b9176
1
.gitignore
vendored
1
.gitignore
vendored
@ -14,3 +14,4 @@ server.pem
|
||||
server.crt
|
||||
instructions-debian.txt
|
||||
cl.md
|
||||
telegram/
|
||||
|
34
api.go
34
api.go
@ -549,7 +549,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
||||
}
|
||||
if len(addresses) != 0 {
|
||||
go func(reason string, addresses []string) {
|
||||
var msg *Email
|
||||
var msg *Message
|
||||
var err error
|
||||
if req.Enabled {
|
||||
msg, err = app.email.constructEnabled(reason, app, false)
|
||||
@ -1580,7 +1580,7 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
||||
id := gc.Param("id")
|
||||
var content string
|
||||
var err error
|
||||
var msg *Email
|
||||
var msg *Message
|
||||
var variables []string
|
||||
var conditionals []string
|
||||
var values map[string]interface{}
|
||||
@ -1874,6 +1874,36 @@ func (app *appContext) ServeLang(gc *gin.Context) {
|
||||
respondBool(400, false, gc)
|
||||
}
|
||||
|
||||
// @Summary Returns true/false on whether or not a telegram PIN was verified.
|
||||
// @Produce json
|
||||
// @Success 200 {object} boolResponse
|
||||
// @Success 401 {object} boolResponse
|
||||
// @Param pin path string true "PIN code to check"
|
||||
// @Param invCode path string true "invite Code"
|
||||
// @Router /invite/{invCode}/telegram/verified/{pin} [get]
|
||||
// @tags Other
|
||||
func (app *appContext) TelegramVerified(gc *gin.Context) {
|
||||
code := gc.Param("invCode")
|
||||
if _, ok := app.storage.invites[code]; !ok {
|
||||
respondBool(401, false, gc)
|
||||
return
|
||||
}
|
||||
pin := gc.Param("pin")
|
||||
tokenIndex := -1
|
||||
for i, v := range app.telegram.verifiedTokens {
|
||||
if v == pin {
|
||||
tokenIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if tokenIndex != -1 {
|
||||
length := len(app.telegram.verifiedTokens)
|
||||
app.telegram.verifiedTokens[length-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[length-1]
|
||||
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:length-1]
|
||||
}
|
||||
respondBool(200, tokenIndex != -1, gc)
|
||||
}
|
||||
|
||||
// @Summary Restarts the program. No response means success.
|
||||
// @Router /restart [post]
|
||||
// @tags Other
|
||||
|
@ -40,7 +40,7 @@ func (app *appContext) loadConfig() error {
|
||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users"} {
|
||||
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users"} {
|
||||
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
|
||||
}
|
||||
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
|
||||
@ -128,6 +128,7 @@ func (app *appContext) loadConfig() error {
|
||||
app.storage.lang.chosenAdminLang = app.config.Section("ui").Key("language-admin").MustString("en-us")
|
||||
app.storage.lang.chosenEmailLang = app.config.Section("email").Key("language").MustString("en-us")
|
||||
app.storage.lang.chosenPWRLang = app.config.Section("password_resets").Key("language").MustString("en-us")
|
||||
app.storage.lang.chosenTelegramLang = app.config.Section("telegram").Key("language").MustString("en-us")
|
||||
|
||||
app.email = NewEmailer(app)
|
||||
|
||||
|
@ -997,6 +997,53 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"telegram": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
"name": "Telegram",
|
||||
"description": "Settings for Telegram signup/notifications"
|
||||
},
|
||||
"settings": {
|
||||
"enabled": {
|
||||
"name": "Enabled",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Enable signup verification through Telegram and the sending of notifications through it."
|
||||
},
|
||||
"required": {
|
||||
"name": "Require on sign-up",
|
||||
"required": false,
|
||||
"required_restart": true,
|
||||
"type": "bool",
|
||||
"value": false,
|
||||
"description": "Require telegram connection on sign-up."
|
||||
},
|
||||
"token": {
|
||||
"name": "API Token",
|
||||
"required": false,
|
||||
"requires_restart": true,
|
||||
"depends_true": "enabled",
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Telegram Bot API Token."
|
||||
},
|
||||
"language": {
|
||||
"name": "Language",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"depends_true": "enabled",
|
||||
"type": "select",
|
||||
"options": [
|
||||
["en-us", "English (US)"]
|
||||
],
|
||||
"value": "en-us",
|
||||
"description": "Default telegram message language. Visit weblate if you'd like to translate."
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
"files": {
|
||||
"order": [],
|
||||
"meta": {
|
||||
@ -1068,6 +1115,14 @@
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info."
|
||||
},
|
||||
"telegram_users": {
|
||||
"name": "Telegram users",
|
||||
"required": false,
|
||||
"requires_restart": false,
|
||||
"type": "text",
|
||||
"value": "",
|
||||
"description": "Stores telegram user IDs and language preferences."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,6 +121,10 @@ div.card:contains(section.banner.footer) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ac {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.inline-block {
|
||||
display: inline-block;
|
||||
}
|
||||
@ -459,6 +463,11 @@ a:hover:not(.lang-link):not(.\~urge), a:active:not(.lang-link):not(.\~urge) {
|
||||
color: var(--color-urge-200);
|
||||
}
|
||||
|
||||
.link-center {
|
||||
display: block;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.search {
|
||||
max-width: 15rem;
|
||||
min-width: 10rem;
|
||||
|
70
email.go
70
email.go
@ -25,13 +25,13 @@ import (
|
||||
)
|
||||
|
||||
// implements email sending, right now via smtp or mailgun.
|
||||
type emailClient interface {
|
||||
send(fromName, fromAddr string, email *Email, address ...string) error
|
||||
type EmailClient interface {
|
||||
Send(fromName, fromAddr string, message *Message, address ...string) error
|
||||
}
|
||||
|
||||
type dummyClient struct{}
|
||||
type DummyClient struct{}
|
||||
|
||||
func (dc *dummyClient) send(fromName, fromAddr string, email *Email, address ...string) error {
|
||||
func (dc *DummyClient) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
fmt.Printf("FROM: %s <%s>\nTO: %s\nTEXT: %s\n", fromName, fromAddr, strings.Join(address, ", "), email.Text)
|
||||
return nil
|
||||
}
|
||||
@ -41,7 +41,7 @@ type Mailgun struct {
|
||||
client *mailgun.MailgunImpl
|
||||
}
|
||||
|
||||
func (mg *Mailgun) send(fromName, fromAddr string, email *Email, address ...string) error {
|
||||
func (mg *Mailgun) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
message := mg.client.NewMessage(
|
||||
fmt.Sprintf("%s <%s>", fromName, fromAddr),
|
||||
email.Subject,
|
||||
@ -67,7 +67,7 @@ type SMTP struct {
|
||||
tlsConfig *tls.Config
|
||||
}
|
||||
|
||||
func (sm *SMTP) send(fromName, fromAddr string, email *Email, address ...string) error {
|
||||
func (sm *SMTP) Send(fromName, fromAddr string, email *Message, address ...string) error {
|
||||
server := fmt.Sprintf("%s:%d", sm.server, sm.port)
|
||||
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
|
||||
var wg sync.WaitGroup
|
||||
@ -93,15 +93,15 @@ func (sm *SMTP) send(fromName, fromAddr string, email *Email, address ...string)
|
||||
return err
|
||||
}
|
||||
|
||||
// Emailer contains the email sender, email content, and methods to construct message content.
|
||||
// Emailer contains the email sender, translations, and methods to construct messages.
|
||||
type Emailer struct {
|
||||
fromAddr, fromName string
|
||||
lang emailLang
|
||||
sender emailClient
|
||||
sender EmailClient
|
||||
}
|
||||
|
||||
// Email stores content.
|
||||
type Email struct {
|
||||
// Message stores content.
|
||||
type Message struct {
|
||||
Subject string `json:"subject"`
|
||||
HTML string `json:"html"`
|
||||
Text string `json:"text"`
|
||||
@ -154,7 +154,7 @@ func NewEmailer(app *appContext) *Emailer {
|
||||
} else if method == "mailgun" {
|
||||
emailer.NewMailgun(app.config.Section("mailgun").Key("api_url").String(), app.config.Section("mailgun").Key("api_key").String())
|
||||
} else if method == "dummy" {
|
||||
emailer.sender = &dummyClient{}
|
||||
emailer.sender = &DummyClient{}
|
||||
}
|
||||
return emailer
|
||||
}
|
||||
@ -267,8 +267,8 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -290,8 +290,8 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
|
||||
return email, nil
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Email, error) {
|
||||
email := &Email{Subject: subject}
|
||||
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Message, error) {
|
||||
email := &Message{Subject: subject}
|
||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
||||
text := stripMarkdown(md)
|
||||
@ -338,8 +338,8 @@ func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("invite_emails").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
||||
}
|
||||
template := emailer.inviteValues(code, invite, app, noSub)
|
||||
@ -377,8 +377,8 @@ func (emailer *Emailer) expiryValues(code string, invite Invite, app *appContext
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: emailer.lang.InviteExpiry.get("title"),
|
||||
}
|
||||
var err error
|
||||
@ -431,8 +431,8 @@ func (emailer *Emailer) createdValues(code, username, address string, invite Inv
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: emailer.lang.UserCreated.get("title"),
|
||||
}
|
||||
template := emailer.createdValues(code, username, address, invite, app, noSub)
|
||||
@ -505,8 +505,8 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
|
||||
}
|
||||
template := emailer.resetValues(pwr, app, noSub)
|
||||
@ -546,8 +546,8 @@ func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -587,8 +587,8 @@ func (emailer *Emailer) disabledValues(reason string, app *appContext, noSub boo
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("disable_enable").Key("subject_disabled").MustString(emailer.lang.UserDisabled.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -628,8 +628,8 @@ func (emailer *Emailer) enabledValues(reason string, app *appContext, noSub bool
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("disable_enable").Key("subject_enabled").MustString(emailer.lang.UserEnabled.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -683,8 +683,8 @@ func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *ap
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -728,8 +728,8 @@ func (emailer *Emailer) userExpiredValues(app *appContext, noSub bool) map[strin
|
||||
return template
|
||||
}
|
||||
|
||||
func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Email, error) {
|
||||
email := &Email{
|
||||
func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Message, error) {
|
||||
email := &Message{
|
||||
Subject: app.config.Section("user_expiry").Key("subject").MustString(emailer.lang.UserExpired.get("title")),
|
||||
}
|
||||
var err error
|
||||
@ -752,6 +752,6 @@ func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Emai
|
||||
}
|
||||
|
||||
// calls the send method in the underlying emailClient.
|
||||
func (emailer *Emailer) send(email *Email, address ...string) error {
|
||||
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
|
||||
func (emailer *Emailer) send(email *Message, address ...string) error {
|
||||
return emailer.sender.Send(emailer.fromName, emailer.fromAddr, email, address...)
|
||||
}
|
||||
|
13
go.mod
13
go.mod
@ -17,29 +17,28 @@ require (
|
||||
github.com/gin-contrib/pprof v1.3.0
|
||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||
github.com/gin-gonic/gin v1.6.3
|
||||
github.com/go-chi/chi v4.1.2+incompatible // indirect
|
||||
github.com/go-openapi/spec v0.20.3 // indirect
|
||||
github.com/go-openapi/swag v0.19.15 // indirect
|
||||
github.com/go-playground/validator/v10 v10.4.1 // indirect
|
||||
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
|
||||
github.com/golang/protobuf v1.4.3 // indirect
|
||||
github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd
|
||||
github.com/google/uuid v1.1.2 // indirect
|
||||
github.com/hrfee/jfa-go/common v0.0.0-20210105184019-fdc97b4e86cc
|
||||
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-00010101000000-000000000000 // indirect
|
||||
github.com/hrfee/jfa-go/logger v0.0.0-00010101000000-000000000000
|
||||
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
|
||||
github.com/hrfee/mediabrowser v0.3.3
|
||||
github.com/itchyny/timefmt-go v0.1.2
|
||||
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4
|
||||
github.com/mailgun/mailgun-go/v4 v4.3.0
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
|
||||
github.com/swaggo/gin-swagger v1.3.0
|
||||
github.com/swaggo/swag v1.7.0 // indirect
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
|
||||
github.com/ugorji/go v1.2.0 // indirect
|
||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
|
||||
|
26
go.sum
26
go.sum
@ -58,9 +58,6 @@ github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmC
|
||||
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
||||
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
|
||||
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
|
||||
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
|
||||
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||
@ -96,6 +93,8 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
|
||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
||||
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/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
||||
@ -112,8 +111,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8 h1:nWU6p08f1VgIalT6iZyqXi4o5cZsz4X6qa87nusfcsc=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd h1:0b8AqsWQb6A0jjx80UXLG/uMTXQkGD0IGuXWqsrNz1M=
|
||||
github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
@ -127,12 +126,14 @@ github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
|
||||
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
|
||||
github.com/hrfee/mediabrowser v0.3.3 h1:7E05uiol8hh2ytKn3WVLrUIvHAyifYEIy3Y5qtuNh8I=
|
||||
github.com/hrfee/mediabrowser v0.3.3/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
||||
github.com/itchyny/timefmt-go v0.1.2 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
|
||||
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
||||
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible h1:CL0ooBNfbNyJTJATno+m0h+zM5bW6v7fKlboKUGP/dI=
|
||||
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
@ -156,8 +157,8 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
|
||||
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
||||
github.com/mailgun/mailgun-go/v4 v4.3.0 h1:9nAF7LI3k6bfDPbMZQMMl63Q8/vs+dr1FUN8eR1XMhk=
|
||||
github.com/mailgun/mailgun-go/v4 v4.3.0/go.mod h1:fWuBI2iaS/pSSyo6+EBpHjatQO3lV8onwqcRy7joSJI=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1 h1:XrQQ/ZgqFvINRKy+eBqowLl7k3pQO6OCLpKphliMOFs=
|
||||
github.com/mailgun/mailgun-go/v4 v4.5.1/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -180,9 +181,8 @@ github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
|
||||
@ -197,8 +197,6 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykE
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
@ -216,6 +214,8 @@ github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+t
|
||||
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
|
||||
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/ugorji/go v1.1.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||
|
@ -14,6 +14,9 @@
|
||||
window.userExpiryHours = {{ .userExpiryHours }};
|
||||
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
||||
window.userExpiryMessage = {{ .userExpiryMessage }};
|
||||
window.telegramEnabled = {{ .telegramEnabled }};
|
||||
window.telegramRequired = {{ .telegramRequired }};
|
||||
window.telegramPIN = "{{ .telegramPIN }}";
|
||||
</script>
|
||||
<script src="js/form.js" type="module"></script>
|
||||
{{ end }}
|
||||
|
@ -19,6 +19,24 @@
|
||||
<p class="content mb-1">{{ .strings.confirmationRequiredMessage }}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{ if .telegramEnabled }}
|
||||
<div id="modal-telegram" class="modal">
|
||||
<div class="modal-content card">
|
||||
<span class="heading mb-1">{{ .strings.linkTelegram }}</span>
|
||||
<p class="content mb-1">{{ .strings.sendPIN }}</p>
|
||||
<h1 class="ac">{{ .telegramPIN }}</h1>
|
||||
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
|
||||
<span class="shield ~info mr-1">
|
||||
<span class="icon">
|
||||
<i class="ri-telegram-line"></i>
|
||||
</span>
|
||||
</span>
|
||||
@{{ .telegramUsername }}
|
||||
</a>
|
||||
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">.</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
<span class="dropdown" tabindex="0" id="lang-dropdown">
|
||||
<span class="button ~urge dropdown-button">
|
||||
<i class="ri-global-line"></i>
|
||||
@ -29,6 +47,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<div id="notification-box"></div>
|
||||
<div class="page-container">
|
||||
<div class="card ~neutral !low">
|
||||
<div class="row baseline">
|
||||
@ -48,7 +67,9 @@
|
||||
|
||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
||||
<input type="email" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
|
||||
|
||||
{{ if .telegramEnabled }}
|
||||
<span class="button ~info !normal full-width center" id="link-telegram">{{ .strings.linkTelegram }}</span>
|
||||
{{ end }}
|
||||
<label class="label supra" for="create-password">{{ .strings.password }}</label>
|
||||
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
|
||||
|
||||
|
@ -13,7 +13,7 @@ const binaryType = "internal"
|
||||
//go:embed data data/html data/web data/web/css data/web/js
|
||||
var loFS embed.FS
|
||||
|
||||
//go:embed lang/common lang/admin lang/email lang/form lang/setup lang/pwreset
|
||||
//go:embed lang/common lang/admin lang/email lang/form lang/setup lang/pwreset lang/telegram
|
||||
var laFS embed.FS
|
||||
|
||||
var langFS rewriteFS
|
||||
|
17
lang.go
17
lang.go
@ -136,6 +136,23 @@ func (ls *setupLangs) getOptions() [][2]string {
|
||||
return opts
|
||||
}
|
||||
|
||||
type telegramLangs map[string]telegramLang
|
||||
|
||||
type telegramLang struct {
|
||||
Meta langMeta `json:"meta"`
|
||||
Strings langSection `json:"strings"`
|
||||
}
|
||||
|
||||
func (ts *telegramLangs) getOptions() [][2]string {
|
||||
opts := make([][2]string, len(*ts))
|
||||
i := 0
|
||||
for key, lang := range *ts {
|
||||
opts[i] = [2]string{key, lang.Meta.Name}
|
||||
i++
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
type langSection map[string]string
|
||||
type tmpl map[string]string
|
||||
|
||||
|
@ -17,11 +17,14 @@
|
||||
"successContinueButton": "Continue",
|
||||
"confirmationRequired": "Email confirmation required",
|
||||
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
|
||||
"yourAccountIsValidUntil": "Your account will be valid until {date}."
|
||||
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
|
||||
"linkTelegram": "Link Telegram",
|
||||
"sendPIN": "Send the PIN below to the bot, then come back here to link your account."
|
||||
},
|
||||
"notifications": {
|
||||
"errorUserExists": "User already exists.",
|
||||
"errorInvalidCode": "Invalid invite code."
|
||||
"errorInvalidCode": "Invalid invite code.",
|
||||
"telegramVerified": "Telegram account verified."
|
||||
},
|
||||
"validationStrings": {
|
||||
"length": {
|
||||
|
16
main.go
16
main.go
@ -93,6 +93,7 @@ type appContext struct {
|
||||
storage Storage
|
||||
validator Validator
|
||||
email *Emailer
|
||||
telegram *TelegramDaemon
|
||||
info, debug, err logger.Logger
|
||||
host string
|
||||
port int
|
||||
@ -257,6 +258,7 @@ func start(asDaemon, firstCall bool) {
|
||||
app.storage.lang.FormPath = "form"
|
||||
app.storage.lang.AdminPath = "admin"
|
||||
app.storage.lang.EmailPath = "email"
|
||||
app.storage.lang.TelegramPath = "telegram"
|
||||
app.storage.lang.PasswordResetPath = "pwreset"
|
||||
externalLang := app.config.Section("files").Key("lang_files").MustString("")
|
||||
var err error
|
||||
@ -325,6 +327,10 @@ func start(asDaemon, firstCall bool) {
|
||||
if err := app.storage.loadUsers(); err != nil {
|
||||
app.err.Printf("Failed to load Users: %v", err)
|
||||
}
|
||||
app.storage.telegram_path = app.config.Section("files").Key("telegram_users").String()
|
||||
if err := app.storage.loadTelegramUsers(); err != nil {
|
||||
app.err.Printf("Failed to load Telegram users: %v", err)
|
||||
}
|
||||
|
||||
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
||||
app.storage.loadProfiles()
|
||||
@ -541,6 +547,16 @@ func start(asDaemon, firstCall bool) {
|
||||
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
||||
go app.checkForUpdates()
|
||||
}
|
||||
|
||||
if app.config.Section("telegram").Key("enabled").MustBool(false) {
|
||||
app.telegram, err = newTelegramDaemon(app)
|
||||
if err != nil {
|
||||
app.err.Printf("Failed to authenticate with Telegram: %v", err)
|
||||
} else {
|
||||
go app.telegram.run()
|
||||
defer app.telegram.Shutdown()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
debugMode = false
|
||||
address = "0.0.0.0:8056"
|
||||
|
@ -118,6 +118,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
||||
router.POST(p+"/newUser", app.NewUser)
|
||||
router.Use(static.Serve(p+"/invite/", app.webFS))
|
||||
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
||||
if app.config.Section("telegram").Key("enabled").MustBool(false) {
|
||||
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerified)
|
||||
}
|
||||
}
|
||||
if *SWAGGER {
|
||||
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
||||
|
157
storage.go
157
storage.go
@ -15,18 +15,25 @@ import (
|
||||
)
|
||||
|
||||
type Storage struct {
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path string
|
||||
users map[string]time.Time
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
emails, displayprefs, ombi_template map[string]interface{}
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
invitesLock, usersLock sync.Mutex
|
||||
timePattern string
|
||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path, telegram_path string
|
||||
users map[string]time.Time
|
||||
invites Invites
|
||||
profiles map[string]Profile
|
||||
defaultProfile string
|
||||
emails, displayprefs, ombi_template map[string]interface{}
|
||||
telegram map[int64]TelegramUser
|
||||
customEmails customEmails
|
||||
policy mediabrowser.Policy
|
||||
configuration mediabrowser.Configuration
|
||||
lang Lang
|
||||
invitesLock, usersLock sync.Mutex
|
||||
}
|
||||
|
||||
type TelegramUser struct {
|
||||
ChatID int64
|
||||
Username string
|
||||
Lang string
|
||||
}
|
||||
|
||||
type customEmails struct {
|
||||
@ -81,23 +88,26 @@ type Invite struct {
|
||||
}
|
||||
|
||||
type Lang struct {
|
||||
AdminPath string
|
||||
chosenAdminLang string
|
||||
Admin adminLangs
|
||||
AdminJSON map[string]string
|
||||
FormPath string
|
||||
chosenFormLang string
|
||||
Form formLangs
|
||||
PasswordResetPath string
|
||||
chosenPWRLang string
|
||||
PasswordReset pwrLangs
|
||||
EmailPath string
|
||||
chosenEmailLang string
|
||||
Email emailLangs
|
||||
CommonPath string
|
||||
Common commonLangs
|
||||
SetupPath string
|
||||
Setup setupLangs
|
||||
AdminPath string
|
||||
chosenAdminLang string
|
||||
Admin adminLangs
|
||||
AdminJSON map[string]string
|
||||
FormPath string
|
||||
chosenFormLang string
|
||||
Form formLangs
|
||||
PasswordResetPath string
|
||||
chosenPWRLang string
|
||||
PasswordReset pwrLangs
|
||||
EmailPath string
|
||||
chosenEmailLang string
|
||||
Email emailLangs
|
||||
CommonPath string
|
||||
Common commonLangs
|
||||
SetupPath string
|
||||
Setup setupLangs
|
||||
chosenTelegramLang string
|
||||
TelegramPath string
|
||||
Telegram telegramLangs
|
||||
}
|
||||
|
||||
func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
||||
@ -118,6 +128,10 @@ func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
||||
return
|
||||
}
|
||||
err = st.loadLangEmail(filesystems...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = st.loadLangTelegram(filesystems...)
|
||||
return
|
||||
}
|
||||
|
||||
@ -620,6 +634,83 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *Storage) loadLangTelegram(filesystems ...fs.FS) error {
|
||||
st.lang.Telegram = map[string]telegramLang{}
|
||||
var english telegramLang
|
||||
loadedLangs := make([]map[string]bool, len(filesystems))
|
||||
var load loadLangFunc
|
||||
load = func(fsIndex int, fname string) error {
|
||||
filesystem := filesystems[fsIndex]
|
||||
index := strings.TrimSuffix(fname, filepath.Ext(fname))
|
||||
lang := telegramLang{}
|
||||
f, err := fs.ReadFile(filesystem, FSJoin(st.lang.TelegramPath, fname))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if substituteStrings != "" {
|
||||
f = []byte(strings.ReplaceAll(string(f), "Jellyfin", substituteStrings))
|
||||
}
|
||||
err = json.Unmarshal(f, &lang)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
st.lang.Common.patchCommon(&lang.Strings, index)
|
||||
if fname != "en-us.json" {
|
||||
if lang.Meta.Fallback != "" {
|
||||
fallback, ok := st.lang.Telegram[lang.Meta.Fallback]
|
||||
err = nil
|
||||
if !ok {
|
||||
err = load(fsIndex, lang.Meta.Fallback+".json")
|
||||
fallback = st.lang.Telegram[lang.Meta.Fallback]
|
||||
}
|
||||
if err == nil {
|
||||
loadedLangs[fsIndex][lang.Meta.Fallback+".json"] = true
|
||||
patchLang(&lang.Strings, &fallback.Strings, &english.Strings)
|
||||
}
|
||||
}
|
||||
if (lang.Meta.Fallback != "" && err != nil) || lang.Meta.Fallback == "" {
|
||||
patchLang(&lang.Strings, &english.Strings)
|
||||
}
|
||||
}
|
||||
st.lang.Telegram[index] = lang
|
||||
return nil
|
||||
}
|
||||
engFound := false
|
||||
var err error
|
||||
for i := range filesystems {
|
||||
loadedLangs[i] = map[string]bool{}
|
||||
err = load(i, "en-us.json")
|
||||
if err == nil {
|
||||
engFound = true
|
||||
}
|
||||
loadedLangs[i]["en-us.json"] = true
|
||||
}
|
||||
if !engFound {
|
||||
return err
|
||||
}
|
||||
english = st.lang.Telegram["en-us"]
|
||||
telegramLoaded := false
|
||||
for i := range filesystems {
|
||||
files, err := fs.ReadDir(filesystems[i], st.lang.TelegramPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, f := range files {
|
||||
if !loadedLangs[i][f.Name()] {
|
||||
err = load(i, f.Name())
|
||||
if err == nil {
|
||||
telegramLoaded = true
|
||||
loadedLangs[i][f.Name()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !telegramLoaded {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Invites map[string]Invite
|
||||
|
||||
func (st *Storage) loadInvites() error {
|
||||
@ -665,6 +756,14 @@ func (st *Storage) storeEmails() error {
|
||||
return storeJSON(st.emails_path, st.emails)
|
||||
}
|
||||
|
||||
func (st *Storage) loadTelegramUsers() error {
|
||||
return loadJSON(st.telegram_path, &st.telegram)
|
||||
}
|
||||
|
||||
func (st *Storage) storeTelegramUsers() error {
|
||||
return storeJSON(st.telegram_path, st.telegram)
|
||||
}
|
||||
|
||||
func (st *Storage) loadCustomEmails() error {
|
||||
return loadJSON(st.customEmails_path, &st.customEmails)
|
||||
}
|
||||
|
201
telegram.go
Normal file
201
telegram.go
Normal file
@ -0,0 +1,201 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
tg "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
type TelegramDaemon struct {
|
||||
Stopped bool
|
||||
ShutdownChannel chan string
|
||||
bot *tg.BotAPI
|
||||
username string
|
||||
tokens []string
|
||||
verifiedTokens []string
|
||||
link string
|
||||
app *appContext
|
||||
}
|
||||
|
||||
func newTelegramDaemon(app *appContext) (*TelegramDaemon, error) {
|
||||
token := app.config.Section("telegram").Key("token").String()
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("token was blank")
|
||||
}
|
||||
bot, err := tg.NewBotAPI(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &TelegramDaemon{
|
||||
Stopped: false,
|
||||
ShutdownChannel: make(chan string),
|
||||
bot: bot,
|
||||
username: bot.Self.UserName,
|
||||
tokens: []string{},
|
||||
verifiedTokens: []string{},
|
||||
link: "https://t.me/" + bot.Self.UserName,
|
||||
app: app,
|
||||
}, nil
|
||||
}
|
||||
|
||||
var runes = []rune("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
|
||||
// NewAuthToken generates an 8-character pin in the form "A1-2B-CD".
|
||||
func (t *TelegramDaemon) NewAuthToken() string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
pin := make([]rune, 8)
|
||||
for i := range pin {
|
||||
if i == 2 || i == 5 {
|
||||
pin[i] = '-'
|
||||
} else {
|
||||
pin[i] = runes[rand.Intn(len(runes))]
|
||||
}
|
||||
}
|
||||
t.tokens = append(t.tokens, string(pin))
|
||||
return string(pin)
|
||||
}
|
||||
|
||||
func (t *TelegramDaemon) run() {
|
||||
t.app.info.Println("Starting Telegram bot daemon")
|
||||
u := tg.NewUpdate(0)
|
||||
u.Timeout = 60
|
||||
updates, err := t.bot.GetUpdatesChan(u)
|
||||
if err != nil {
|
||||
t.app.err.Printf("Failed to start Telegram daemon: %v", err)
|
||||
return
|
||||
}
|
||||
for {
|
||||
var upd tg.Update
|
||||
select {
|
||||
case upd = <-updates:
|
||||
if upd.Message == nil {
|
||||
continue
|
||||
}
|
||||
sects := strings.Split(upd.Message.Text, " ")
|
||||
if len(sects) == 0 {
|
||||
continue
|
||||
}
|
||||
lang := t.app.storage.lang.chosenTelegramLang
|
||||
user, ok := t.app.storage.telegram[upd.Message.Chat.ID]
|
||||
if !ok {
|
||||
user := TelegramUser{
|
||||
Username: upd.Message.Chat.UserName,
|
||||
ChatID: upd.Message.Chat.ID,
|
||||
Lang: "",
|
||||
}
|
||||
t.app.storage.telegram[upd.Message.Chat.ID] = user
|
||||
err := t.app.storage.storeTelegramUsers()
|
||||
if err != nil {
|
||||
t.app.err.Printf("Failed to store Telegram users: %v", err)
|
||||
}
|
||||
}
|
||||
if user.Lang != "" {
|
||||
lang = user.Lang
|
||||
} else {
|
||||
for code := range t.app.storage.lang.Telegram {
|
||||
if code[:2] == upd.Message.From.LanguageCode {
|
||||
lang = code
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
switch msg := sects[0]; msg {
|
||||
case "/start":
|
||||
content := t.app.storage.lang.Telegram[lang].Strings.get("startMessage") + "\n"
|
||||
content += t.app.storage.lang.Telegram[lang].Strings.get("languageMessage")
|
||||
err := t.Reply(&upd, content)
|
||||
if err != nil {
|
||||
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
|
||||
}
|
||||
continue
|
||||
case "/lang":
|
||||
if len(sects) == 1 {
|
||||
list := "/lang <lang>\n"
|
||||
for code := range t.app.storage.lang.Telegram {
|
||||
list += fmt.Sprintf("%s: %s\n", code, t.app.storage.lang.Telegram[code].Meta.Name)
|
||||
}
|
||||
err := t.Reply(&upd, list)
|
||||
if err != nil {
|
||||
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, ok := t.app.storage.lang.Telegram[sects[1]]; ok {
|
||||
user.Lang = sects[1]
|
||||
t.app.storage.telegram[upd.Message.Chat.ID] = user
|
||||
err := t.app.storage.storeTelegramUsers()
|
||||
if err != nil {
|
||||
t.app.err.Printf("Failed to store Telegram users: %v", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
default:
|
||||
tokenIndex := -1
|
||||
for i, token := range t.tokens {
|
||||
if upd.Message.Text == token {
|
||||
tokenIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if tokenIndex == -1 {
|
||||
err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"))
|
||||
if err != nil {
|
||||
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
err := t.QuoteReply(&upd, t.app.storage.lang.Telegram[lang].Strings.get("success"))
|
||||
if err != nil {
|
||||
t.app.err.Printf("Telegram: Failed to send message to \"%s\": %v", upd.Message.From.UserName, err)
|
||||
}
|
||||
t.verifiedTokens = append(t.verifiedTokens, upd.Message.Text)
|
||||
t.tokens[len(t.tokens)-1], t.tokens[tokenIndex] = t.tokens[tokenIndex], t.tokens[len(t.tokens)-1]
|
||||
t.tokens = t.tokens[:len(t.tokens)-1]
|
||||
}
|
||||
|
||||
case <-t.ShutdownChannel:
|
||||
t.ShutdownChannel <- "Down"
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TelegramDaemon) Reply(upd *tg.Update, content string) error {
|
||||
msg := tg.NewMessage((*upd).Message.Chat.ID, content)
|
||||
_, err := t.bot.Send(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
func (t *TelegramDaemon) QuoteReply(upd *tg.Update, content string) error {
|
||||
msg := tg.NewMessage((*upd).Message.Chat.ID, content)
|
||||
msg.ReplyToMessageID = (*upd).Message.MessageID
|
||||
_, err := t.bot.Send(msg)
|
||||
return err
|
||||
}
|
||||
|
||||
// Send adds compatibility with EmailClient, fromName/fromAddr are discarded, message.Text is used, addresses are Chat IDs as strings.
|
||||
func (t *TelegramDaemon) Send(fromName, fromAddr string, message *Message, address ...string) error {
|
||||
for _, addr := range address {
|
||||
ChatID, err := strconv.ParseInt(addr, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg := tg.NewMessage(ChatID, message.Text)
|
||||
_, err = t.bot.Send(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TelegramDaemon) Shutdown() {
|
||||
t.Stopped = true
|
||||
t.ShutdownChannel <- "Down"
|
||||
<-t.ShutdownChannel
|
||||
close(t.ShutdownChannel)
|
||||
}
|
43
ts/form.ts
43
ts/form.ts
@ -1,15 +1,20 @@
|
||||
import { Modal } from "./modules/modal.js";
|
||||
import { notificationBox } from "./modules/common.js";
|
||||
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
|
||||
import { loadLangSelector } from "./modules/lang.js";
|
||||
|
||||
interface formWindow extends Window {
|
||||
validationStrings: pwValStrings;
|
||||
invalidPassword: string;
|
||||
modal: Modal;
|
||||
successModal: Modal;
|
||||
telegramModal: Modal;
|
||||
confirmationModal: Modal
|
||||
code: string;
|
||||
messages: { [key: string]: string };
|
||||
confirmation: boolean;
|
||||
confirmationModal: Modal
|
||||
telegramEnabled: boolean;
|
||||
telegramRequired: boolean;
|
||||
telegramPIN: string;
|
||||
userExpiryEnabled: boolean;
|
||||
userExpiryMonths: number;
|
||||
userExpiryDays: number;
|
||||
@ -34,7 +39,37 @@ interface pwValStrings {
|
||||
|
||||
loadLangSelector("form");
|
||||
|
||||
window.modal = new Modal(document.getElementById("modal-success"), true);
|
||||
window.notifications = new notificationBox(document.getElementById("notification-box") as HTMLDivElement);
|
||||
|
||||
window.successModal = new Modal(document.getElementById("modal-success"), true);
|
||||
|
||||
if (window.telegramEnabled) {
|
||||
window.telegramModal = new Modal(document.getElementById("modal-telegram"), window.telegramRequired);
|
||||
(document.getElementById("link-telegram") as HTMLSpanElement).onclick = () => {
|
||||
const waiting = document.getElementById("telegram-waiting") as HTMLSpanElement;
|
||||
toggleLoader(waiting);
|
||||
window.telegramModal.show();
|
||||
const checkVerified = () => _get("/invite/" + window.code + "/telegram/verified/" + window.telegramPIN, null, (req: XMLHttpRequest) => {
|
||||
if (req.readyState == 4) {
|
||||
if (req.status == 401) {
|
||||
window.telegramModal.close();
|
||||
window.notifications.customError("invalidCodeError", window.lang.notif("errorInvalidCode"));
|
||||
return;
|
||||
} else if (req.status == 200) {
|
||||
if (req.response["success"] as boolean) {
|
||||
toggleLoader(waiting);
|
||||
window.telegramModal.close()
|
||||
window.notifications.customSuccess("accountVerified", window.lang.notif("telegramVerified"))
|
||||
} else {
|
||||
setTimeout(checkVerified, 1500);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
checkVerified();
|
||||
};
|
||||
}
|
||||
|
||||
if (window.confirmation) {
|
||||
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
||||
}
|
||||
@ -130,7 +165,7 @@ const create = (event: SubmitEvent) => {
|
||||
if (!vals[type]) { valid = false; }
|
||||
}
|
||||
if (req.status == 200 && valid) {
|
||||
window.modal.show();
|
||||
window.successModal.show();
|
||||
} else {
|
||||
submitSpan.classList.add("~critical");
|
||||
submitSpan.classList.remove("~urge");
|
||||
|
12
views.go
12
views.go
@ -259,7 +259,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
if strings.Contains(email, "Failed") {
|
||||
email = ""
|
||||
}
|
||||
gcHTML(gc, http.StatusOK, "form-loader.html", gin.H{
|
||||
data := gin.H{
|
||||
"urlBase": app.getURLBase(gc),
|
||||
"cssClass": app.cssClass,
|
||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||
@ -282,7 +282,15 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||
"userExpiryMinutes": inv.UserMinutes,
|
||||
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
||||
"langName": lang,
|
||||
})
|
||||
"telegramEnabled": app.config.Section("telegram").Key("enabled").MustBool(false),
|
||||
}
|
||||
if data["telegramEnabled"].(bool) {
|
||||
data["telegramPIN"] = app.telegram.NewAuthToken()
|
||||
data["telegramUsername"] = app.telegram.username
|
||||
data["telegramURL"] = app.telegram.link
|
||||
data["telegramRequired"] = app.config.Section("telegram").Key("required").MustBool(false)
|
||||
}
|
||||
gcHTML(gc, http.StatusOK, "form-loader.html", data)
|
||||
}
|
||||
|
||||
func (app *appContext) NoRouteHandler(gc *gin.Context) {
|
||||
|
Loading…
Reference in New Issue
Block a user