mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10: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
|
server.crt
|
||||||
instructions-debian.txt
|
instructions-debian.txt
|
||||||
cl.md
|
cl.md
|
||||||
|
telegram/
|
||||||
|
34
api.go
34
api.go
@ -549,7 +549,7 @@ func (app *appContext) EnableDisableUsers(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
if len(addresses) != 0 {
|
if len(addresses) != 0 {
|
||||||
go func(reason string, addresses []string) {
|
go func(reason string, addresses []string) {
|
||||||
var msg *Email
|
var msg *Message
|
||||||
var err error
|
var err error
|
||||||
if req.Enabled {
|
if req.Enabled {
|
||||||
msg, err = app.email.constructEnabled(reason, app, false)
|
msg, err = app.email.constructEnabled(reason, app, false)
|
||||||
@ -1580,7 +1580,7 @@ func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
|
|||||||
id := gc.Param("id")
|
id := gc.Param("id")
|
||||||
var content string
|
var content string
|
||||||
var err error
|
var err error
|
||||||
var msg *Email
|
var msg *Message
|
||||||
var variables []string
|
var variables []string
|
||||||
var conditionals []string
|
var conditionals []string
|
||||||
var values map[string]interface{}
|
var values map[string]interface{}
|
||||||
@ -1874,6 +1874,36 @@ func (app *appContext) ServeLang(gc *gin.Context) {
|
|||||||
respondBool(400, false, gc)
|
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.
|
// @Summary Restarts the program. No response means success.
|
||||||
// @Router /restart [post]
|
// @Router /restart [post]
|
||||||
// @tags Other
|
// @tags Other
|
||||||
|
@ -40,7 +40,7 @@ func (app *appContext) loadConfig() error {
|
|||||||
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
|
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.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(""), "/")
|
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.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.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.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)
|
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": {
|
"files": {
|
||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
@ -1068,6 +1115,14 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"value": "",
|
"value": "",
|
||||||
"description": "JSON file generated by program in settings, different from email_html/email_text. See wiki for more info."
|
"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;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ac {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.inline-block {
|
.inline-block {
|
||||||
display: 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);
|
color: var(--color-urge-200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link-center {
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
max-width: 15rem;
|
max-width: 15rem;
|
||||||
min-width: 10rem;
|
min-width: 10rem;
|
||||||
|
70
email.go
70
email.go
@ -25,13 +25,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// implements email sending, right now via smtp or mailgun.
|
// implements email sending, right now via smtp or mailgun.
|
||||||
type emailClient interface {
|
type EmailClient interface {
|
||||||
send(fromName, fromAddr string, email *Email, address ...string) error
|
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)
|
fmt.Printf("FROM: %s <%s>\nTO: %s\nTEXT: %s\n", fromName, fromAddr, strings.Join(address, ", "), email.Text)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ type Mailgun struct {
|
|||||||
client *mailgun.MailgunImpl
|
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(
|
message := mg.client.NewMessage(
|
||||||
fmt.Sprintf("%s <%s>", fromName, fromAddr),
|
fmt.Sprintf("%s <%s>", fromName, fromAddr),
|
||||||
email.Subject,
|
email.Subject,
|
||||||
@ -67,7 +67,7 @@ type SMTP struct {
|
|||||||
tlsConfig *tls.Config
|
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)
|
server := fmt.Sprintf("%s:%d", sm.server, sm.port)
|
||||||
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
|
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
@ -93,15 +93,15 @@ func (sm *SMTP) send(fromName, fromAddr string, email *Email, address ...string)
|
|||||||
return err
|
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 {
|
type Emailer struct {
|
||||||
fromAddr, fromName string
|
fromAddr, fromName string
|
||||||
lang emailLang
|
lang emailLang
|
||||||
sender emailClient
|
sender EmailClient
|
||||||
}
|
}
|
||||||
|
|
||||||
// Email stores content.
|
// Message stores content.
|
||||||
type Email struct {
|
type Message struct {
|
||||||
Subject string `json:"subject"`
|
Subject string `json:"subject"`
|
||||||
HTML string `json:"html"`
|
HTML string `json:"html"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
@ -154,7 +154,7 @@ func NewEmailer(app *appContext) *Emailer {
|
|||||||
} else if method == "mailgun" {
|
} else if method == "mailgun" {
|
||||||
emailer.NewMailgun(app.config.Section("mailgun").Key("api_url").String(), app.config.Section("mailgun").Key("api_key").String())
|
emailer.NewMailgun(app.config.Section("mailgun").Key("api_url").String(), app.config.Section("mailgun").Key("api_key").String())
|
||||||
} else if method == "dummy" {
|
} else if method == "dummy" {
|
||||||
emailer.sender = &dummyClient{}
|
emailer.sender = &DummyClient{}
|
||||||
}
|
}
|
||||||
return emailer
|
return emailer
|
||||||
}
|
}
|
||||||
@ -267,8 +267,8 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructConfirmation(code, username, key string, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
Subject: app.config.Section("email_confirmation").Key("subject").MustString(emailer.lang.EmailConfirmation.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -290,8 +290,8 @@ func (emailer *Emailer) constructConfirmation(code, username, key string, app *a
|
|||||||
return email, nil
|
return email, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Email, error) {
|
func (emailer *Emailer) constructTemplate(subject, md string, app *appContext) (*Message, error) {
|
||||||
email := &Email{Subject: subject}
|
email := &Message{Subject: subject}
|
||||||
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
renderer := html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
|
||||||
html := markdown.ToHTML([]byte(md), nil, renderer)
|
html := markdown.ToHTML([]byte(md), nil, renderer)
|
||||||
text := stripMarkdown(md)
|
text := stripMarkdown(md)
|
||||||
@ -338,8 +338,8 @@ func (emailer *Emailer) inviteValues(code string, invite Invite, app *appContext
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructInvite(code string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("invite_emails").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
Subject: app.config.Section("invite_emails").Key("subject").MustString(emailer.lang.InviteEmail.get("title")),
|
||||||
}
|
}
|
||||||
template := emailer.inviteValues(code, invite, app, noSub)
|
template := emailer.inviteValues(code, invite, app, noSub)
|
||||||
@ -377,8 +377,8 @@ func (emailer *Emailer) expiryValues(code string, invite Invite, app *appContext
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructExpiry(code string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: emailer.lang.InviteExpiry.get("title"),
|
Subject: emailer.lang.InviteExpiry.get("title"),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -431,8 +431,8 @@ func (emailer *Emailer) createdValues(code, username, address string, invite Inv
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructCreated(code, username, address string, invite Invite, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: emailer.lang.UserCreated.get("title"),
|
Subject: emailer.lang.UserCreated.get("title"),
|
||||||
}
|
}
|
||||||
template := emailer.createdValues(code, username, address, invite, app, noSub)
|
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
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructReset(pwr PasswordReset, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
|
Subject: app.config.Section("password_resets").Key("subject").MustString(emailer.lang.PasswordReset.get("title")),
|
||||||
}
|
}
|
||||||
template := emailer.resetValues(pwr, app, noSub)
|
template := emailer.resetValues(pwr, app, noSub)
|
||||||
@ -546,8 +546,8 @@ func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructDeleted(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
Subject: app.config.Section("deletion").Key("subject").MustString(emailer.lang.UserDeleted.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -587,8 +587,8 @@ func (emailer *Emailer) disabledValues(reason string, app *appContext, noSub boo
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructDisabled(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("disable_enable").Key("subject_disabled").MustString(emailer.lang.UserDisabled.get("title")),
|
Subject: app.config.Section("disable_enable").Key("subject_disabled").MustString(emailer.lang.UserDisabled.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -628,8 +628,8 @@ func (emailer *Emailer) enabledValues(reason string, app *appContext, noSub bool
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructEnabled(reason string, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("disable_enable").Key("subject_enabled").MustString(emailer.lang.UserEnabled.get("title")),
|
Subject: app.config.Section("disable_enable").Key("subject_enabled").MustString(emailer.lang.UserEnabled.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -683,8 +683,8 @@ func (emailer *Emailer) welcomeValues(username string, expiry time.Time, app *ap
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructWelcome(username string, expiry time.Time, app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
|
Subject: app.config.Section("welcome_email").Key("subject").MustString(emailer.lang.WelcomeEmail.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -728,8 +728,8 @@ func (emailer *Emailer) userExpiredValues(app *appContext, noSub bool) map[strin
|
|||||||
return template
|
return template
|
||||||
}
|
}
|
||||||
|
|
||||||
func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Email, error) {
|
func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Message, error) {
|
||||||
email := &Email{
|
email := &Message{
|
||||||
Subject: app.config.Section("user_expiry").Key("subject").MustString(emailer.lang.UserExpired.get("title")),
|
Subject: app.config.Section("user_expiry").Key("subject").MustString(emailer.lang.UserExpired.get("title")),
|
||||||
}
|
}
|
||||||
var err error
|
var err error
|
||||||
@ -752,6 +752,6 @@ func (emailer *Emailer) constructUserExpired(app *appContext, noSub bool) (*Emai
|
|||||||
}
|
}
|
||||||
|
|
||||||
// calls the send method in the underlying emailClient.
|
// calls the send method in the underlying emailClient.
|
||||||
func (emailer *Emailer) send(email *Email, address ...string) error {
|
func (emailer *Emailer) send(email *Message, address ...string) error {
|
||||||
return emailer.sender.send(emailer.fromName, emailer.fromAddr, email, address...)
|
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/pprof v1.3.0
|
||||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||||
github.com/gin-gonic/gin v1.6.3
|
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/spec v0.20.3 // indirect
|
||||||
github.com/go-openapi/swag v0.19.15 // indirect
|
github.com/go-openapi/swag v0.19.15 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.4.1 // 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/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/google/uuid v1.1.2 // indirect
|
||||||
github.com/hrfee/jfa-go/common v0.0.0-20210105184019-fdc97b4e86cc
|
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/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/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
|
||||||
github.com/hrfee/mediabrowser v0.3.3
|
github.com/hrfee/mediabrowser v0.3.3
|
||||||
github.com/itchyny/timefmt-go v0.1.2
|
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/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/mailru/easyjson v0.7.7 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/smartystreets/goconvey v1.6.4 // 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/files v0.0.0-20190704085106-630677cd5c14
|
||||||
github.com/swaggo/gin-swagger v1.3.0
|
github.com/swaggo/gin-swagger v1.3.0
|
||||||
github.com/swaggo/swag v1.7.0 // indirect
|
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/ugorji/go v1.2.0 // indirect
|
||||||
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
github.com/writeas/go-strip-markdown v2.0.1+incompatible
|
||||||
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
|
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.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
|
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/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.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.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
|
||||||
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
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.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 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
|
||||||
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
|
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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
github.com/golang/mock v1.1.1 h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=
|
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
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-20210408062403-ad838ccf8cdd h1:0b8AqsWQb6A0jjx80UXLG/uMTXQkGD0IGuXWqsrNz1M=
|
||||||
github.com/gomarkdown/markdown v0.0.0-20210208175418-bda154fe17d8/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
|
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.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.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/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/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 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
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 h1:7E05uiol8hh2ytKn3WVLrUIvHAyifYEIy3Y5qtuNh8I=
|
||||||
github.com/hrfee/mediabrowser v0.3.3/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
|
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 h1:q0Xa4P5it6K6D7ISsbLAMwx1PnWlixDcJL6/sFs93Hs=
|
||||||
github.com/itchyny/timefmt-go v0.1.2/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
|
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.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
|
||||||
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/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 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
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=
|
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/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 h1:uj4xhotfY92Y1Oa6n6HUiFn87CdoEHYUlTy0+IgbLrs=
|
||||||
github.com/lithammer/shortuuid/v3 v3.0.4/go.mod h1:RviRjexKqIzx/7r1peoAITm6m7gnif/h+0zmolKJjzw=
|
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.5.1 h1:XrQQ/ZgqFvINRKy+eBqowLl7k3pQO6OCLpKphliMOFs=
|
||||||
github.com/mailgun/mailgun-go/v4 v4.3.0/go.mod h1:fWuBI2iaS/pSSyo6+EBpHjatQO3lV8onwqcRy7joSJI=
|
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-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-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/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/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 h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
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.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
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/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 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
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.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 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
|
||||||
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
|
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.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.5-pre/go.mod h1:FwP/aQVg39TXzItUBMwnWp9T9gPQnXw4Poh4/oBQZ/0=
|
||||||
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
window.userExpiryHours = {{ .userExpiryHours }};
|
window.userExpiryHours = {{ .userExpiryHours }};
|
||||||
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
window.userExpiryMinutes = {{ .userExpiryMinutes }};
|
||||||
window.userExpiryMessage = {{ .userExpiryMessage }};
|
window.userExpiryMessage = {{ .userExpiryMessage }};
|
||||||
|
window.telegramEnabled = {{ .telegramEnabled }};
|
||||||
|
window.telegramRequired = {{ .telegramRequired }};
|
||||||
|
window.telegramPIN = "{{ .telegramPIN }}";
|
||||||
</script>
|
</script>
|
||||||
<script src="js/form.js" type="module"></script>
|
<script src="js/form.js" type="module"></script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -19,6 +19,24 @@
|
|||||||
<p class="content mb-1">{{ .strings.confirmationRequiredMessage }}</p>
|
<p class="content mb-1">{{ .strings.confirmationRequiredMessage }}</p>
|
||||||
</div>
|
</div>
|
||||||
</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="dropdown" tabindex="0" id="lang-dropdown">
|
||||||
<span class="button ~urge dropdown-button">
|
<span class="button ~urge dropdown-button">
|
||||||
<i class="ri-global-line"></i>
|
<i class="ri-global-line"></i>
|
||||||
@ -29,6 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
<div id="notification-box"></div>
|
||||||
<div class="page-container">
|
<div class="page-container">
|
||||||
<div class="card ~neutral !low">
|
<div class="card ~neutral !low">
|
||||||
<div class="row baseline">
|
<div class="row baseline">
|
||||||
@ -48,7 +67,9 @@
|
|||||||
|
|
||||||
<label class="label supra" for="create-email">{{ .strings.emailAddress }}</label>
|
<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 }}">
|
<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>
|
<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 }}">
|
<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
|
//go:embed data data/html data/web data/web/css data/web/js
|
||||||
var loFS embed.FS
|
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 laFS embed.FS
|
||||||
|
|
||||||
var langFS rewriteFS
|
var langFS rewriteFS
|
||||||
|
17
lang.go
17
lang.go
@ -136,6 +136,23 @@ func (ls *setupLangs) getOptions() [][2]string {
|
|||||||
return opts
|
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 langSection map[string]string
|
||||||
type tmpl map[string]string
|
type tmpl map[string]string
|
||||||
|
|
||||||
|
@ -17,11 +17,14 @@
|
|||||||
"successContinueButton": "Continue",
|
"successContinueButton": "Continue",
|
||||||
"confirmationRequired": "Email confirmation required",
|
"confirmationRequired": "Email confirmation required",
|
||||||
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
|
"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": {
|
"notifications": {
|
||||||
"errorUserExists": "User already exists.",
|
"errorUserExists": "User already exists.",
|
||||||
"errorInvalidCode": "Invalid invite code."
|
"errorInvalidCode": "Invalid invite code.",
|
||||||
|
"telegramVerified": "Telegram account verified."
|
||||||
},
|
},
|
||||||
"validationStrings": {
|
"validationStrings": {
|
||||||
"length": {
|
"length": {
|
||||||
|
16
main.go
16
main.go
@ -93,6 +93,7 @@ type appContext struct {
|
|||||||
storage Storage
|
storage Storage
|
||||||
validator Validator
|
validator Validator
|
||||||
email *Emailer
|
email *Emailer
|
||||||
|
telegram *TelegramDaemon
|
||||||
info, debug, err logger.Logger
|
info, debug, err logger.Logger
|
||||||
host string
|
host string
|
||||||
port int
|
port int
|
||||||
@ -257,6 +258,7 @@ func start(asDaemon, firstCall bool) {
|
|||||||
app.storage.lang.FormPath = "form"
|
app.storage.lang.FormPath = "form"
|
||||||
app.storage.lang.AdminPath = "admin"
|
app.storage.lang.AdminPath = "admin"
|
||||||
app.storage.lang.EmailPath = "email"
|
app.storage.lang.EmailPath = "email"
|
||||||
|
app.storage.lang.TelegramPath = "telegram"
|
||||||
app.storage.lang.PasswordResetPath = "pwreset"
|
app.storage.lang.PasswordResetPath = "pwreset"
|
||||||
externalLang := app.config.Section("files").Key("lang_files").MustString("")
|
externalLang := app.config.Section("files").Key("lang_files").MustString("")
|
||||||
var err error
|
var err error
|
||||||
@ -325,6 +327,10 @@ func start(asDaemon, firstCall bool) {
|
|||||||
if err := app.storage.loadUsers(); err != nil {
|
if err := app.storage.loadUsers(); err != nil {
|
||||||
app.err.Printf("Failed to load Users: %v", err)
|
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.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
||||||
app.storage.loadProfiles()
|
app.storage.loadProfiles()
|
||||||
@ -541,6 +547,16 @@ func start(asDaemon, firstCall bool) {
|
|||||||
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
||||||
go app.checkForUpdates()
|
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 {
|
} else {
|
||||||
debugMode = false
|
debugMode = false
|
||||||
address = "0.0.0.0:8056"
|
address = "0.0.0.0:8056"
|
||||||
|
@ -118,6 +118,9 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
router.POST(p+"/newUser", app.NewUser)
|
router.POST(p+"/newUser", app.NewUser)
|
||||||
router.Use(static.Serve(p+"/invite/", app.webFS))
|
router.Use(static.Serve(p+"/invite/", app.webFS))
|
||||||
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
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 {
|
if *SWAGGER {
|
||||||
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
app.info.Print(warning("\n\nWARNING: Swagger should not be used on a public instance.\n\n"))
|
||||||
|
101
storage.go
101
storage.go
@ -16,12 +16,13 @@ import (
|
|||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
timePattern string
|
timePattern string
|
||||||
invite_path, emails_path, policy_path, configuration_path, displayprefs_path, ombi_path, profiles_path, customEmails_path, users_path 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
|
users map[string]time.Time
|
||||||
invites Invites
|
invites Invites
|
||||||
profiles map[string]Profile
|
profiles map[string]Profile
|
||||||
defaultProfile string
|
defaultProfile string
|
||||||
emails, displayprefs, ombi_template map[string]interface{}
|
emails, displayprefs, ombi_template map[string]interface{}
|
||||||
|
telegram map[int64]TelegramUser
|
||||||
customEmails customEmails
|
customEmails customEmails
|
||||||
policy mediabrowser.Policy
|
policy mediabrowser.Policy
|
||||||
configuration mediabrowser.Configuration
|
configuration mediabrowser.Configuration
|
||||||
@ -29,6 +30,12 @@ type Storage struct {
|
|||||||
invitesLock, usersLock sync.Mutex
|
invitesLock, usersLock sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TelegramUser struct {
|
||||||
|
ChatID int64
|
||||||
|
Username string
|
||||||
|
Lang string
|
||||||
|
}
|
||||||
|
|
||||||
type customEmails struct {
|
type customEmails struct {
|
||||||
UserCreated customEmail `json:"userCreated"`
|
UserCreated customEmail `json:"userCreated"`
|
||||||
InviteExpiry customEmail `json:"inviteExpiry"`
|
InviteExpiry customEmail `json:"inviteExpiry"`
|
||||||
@ -98,6 +105,9 @@ type Lang struct {
|
|||||||
Common commonLangs
|
Common commonLangs
|
||||||
SetupPath string
|
SetupPath string
|
||||||
Setup setupLangs
|
Setup setupLangs
|
||||||
|
chosenTelegramLang string
|
||||||
|
TelegramPath string
|
||||||
|
Telegram telegramLangs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
||||||
@ -118,6 +128,10 @@ func (st *Storage) loadLang(filesystems ...fs.FS) (err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = st.loadLangEmail(filesystems...)
|
err = st.loadLangEmail(filesystems...)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = st.loadLangTelegram(filesystems...)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -620,6 +634,83 @@ func (st *Storage) loadLangEmail(filesystems ...fs.FS) error {
|
|||||||
return nil
|
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
|
type Invites map[string]Invite
|
||||||
|
|
||||||
func (st *Storage) loadInvites() error {
|
func (st *Storage) loadInvites() error {
|
||||||
@ -665,6 +756,14 @@ func (st *Storage) storeEmails() error {
|
|||||||
return storeJSON(st.emails_path, st.emails)
|
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 {
|
func (st *Storage) loadCustomEmails() error {
|
||||||
return loadJSON(st.customEmails_path, &st.customEmails)
|
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 { Modal } from "./modules/modal.js";
|
||||||
|
import { notificationBox } from "./modules/common.js";
|
||||||
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
|
import { _get, _post, toggleLoader, toDateString } from "./modules/common.js";
|
||||||
import { loadLangSelector } from "./modules/lang.js";
|
import { loadLangSelector } from "./modules/lang.js";
|
||||||
|
|
||||||
interface formWindow extends Window {
|
interface formWindow extends Window {
|
||||||
validationStrings: pwValStrings;
|
validationStrings: pwValStrings;
|
||||||
invalidPassword: string;
|
invalidPassword: string;
|
||||||
modal: Modal;
|
successModal: Modal;
|
||||||
|
telegramModal: Modal;
|
||||||
|
confirmationModal: Modal
|
||||||
code: string;
|
code: string;
|
||||||
messages: { [key: string]: string };
|
messages: { [key: string]: string };
|
||||||
confirmation: boolean;
|
confirmation: boolean;
|
||||||
confirmationModal: Modal
|
telegramEnabled: boolean;
|
||||||
|
telegramRequired: boolean;
|
||||||
|
telegramPIN: string;
|
||||||
userExpiryEnabled: boolean;
|
userExpiryEnabled: boolean;
|
||||||
userExpiryMonths: number;
|
userExpiryMonths: number;
|
||||||
userExpiryDays: number;
|
userExpiryDays: number;
|
||||||
@ -34,7 +39,37 @@ interface pwValStrings {
|
|||||||
|
|
||||||
loadLangSelector("form");
|
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) {
|
if (window.confirmation) {
|
||||||
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
window.confirmationModal = new Modal(document.getElementById("modal-confirmation"), true);
|
||||||
}
|
}
|
||||||
@ -130,7 +165,7 @@ const create = (event: SubmitEvent) => {
|
|||||||
if (!vals[type]) { valid = false; }
|
if (!vals[type]) { valid = false; }
|
||||||
}
|
}
|
||||||
if (req.status == 200 && valid) {
|
if (req.status == 200 && valid) {
|
||||||
window.modal.show();
|
window.successModal.show();
|
||||||
} else {
|
} else {
|
||||||
submitSpan.classList.add("~critical");
|
submitSpan.classList.add("~critical");
|
||||||
submitSpan.classList.remove("~urge");
|
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") {
|
if strings.Contains(email, "Failed") {
|
||||||
email = ""
|
email = ""
|
||||||
}
|
}
|
||||||
gcHTML(gc, http.StatusOK, "form-loader.html", gin.H{
|
data := gin.H{
|
||||||
"urlBase": app.getURLBase(gc),
|
"urlBase": app.getURLBase(gc),
|
||||||
"cssClass": app.cssClass,
|
"cssClass": app.cssClass,
|
||||||
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
@ -282,7 +282,15 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"userExpiryMinutes": inv.UserMinutes,
|
"userExpiryMinutes": inv.UserMinutes,
|
||||||
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
"userExpiryMessage": app.storage.lang.Form[lang].Strings.get("yourAccountIsValidUntil"),
|
||||||
"langName": lang,
|
"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) {
|
func (app *appContext) NoRouteHandler(gc *gin.Context) {
|
||||||
|
Loading…
Reference in New Issue
Block a user