1
0
mirror of https://github.com/hrfee/jfa-go.git synced 2025-01-03 23:10:11 +00:00

Compare commits

...

3 Commits

Author SHA1 Message Date
c560ec0f9f
Merge branch 'main' into telegram 2021-05-08 16:08:20 +01:00
71554e0c85
Telegram: Change user's contact method in accounts
By clicking the cog next to the telegram username, one can select
whether to contact through telegram or email.
2021-05-08 15:53:42 +01:00
b91302ddf8
Invite: fix "none yet" message on users created 2021-05-07 22:41:51 +01:00
12 changed files with 212 additions and 13 deletions

37
api.go
View File

@ -1176,6 +1176,7 @@ func (app *appContext) GetUsers(gc *gin.Context) {
}
if tgUser, ok := app.storage.telegram[jfUser.ID]; ok {
user.Telegram = tgUser.Username
user.NotifyThroughTelegram = tgUser.Contact
}
resp.UserList[i] = user
i++
@ -1991,6 +1992,42 @@ func (app *appContext) TelegramAddUser(gc *gin.Context) {
respondBool(200, true, gc)
}
// @Summary Sets whether to notify a user through telegram or not.
// @Produce json
// @Param telegramNotifyDTO body telegramNotifyDTO true "User's Jellyfin ID and whether or not to notify then through Telegram."
// @Success 200 {object} boolResponse
// @Success 400 {object} boolResponse
// @Success 500 {object} boolResponse
// @Router /users/telegram/notify [post]
// @Security Bearer
// @tags Other
func (app *appContext) TelegramSetNotify(gc *gin.Context) {
var req telegramNotifyDTO
gc.BindJSON(&req)
if req.ID == "" {
respondBool(400, false, gc)
return
}
if tgUser, ok := app.storage.telegram[req.ID]; ok {
tgUser.Contact = req.Enabled
app.storage.telegram[req.ID] = tgUser
if err := app.storage.storeTelegramUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Telegram: Failed to store users: %v", err)
return
}
respondBool(200, true, gc)
msg := ""
if !req.Enabled {
msg = "not"
}
app.debug.Printf("Telegram: User \"%s\" will %s be notified through Telegram.", tgUser.Username, msg)
return
}
app.err.Printf("Telegram: User \"%s\" does not have a telegram account registered.", req.ID)
respondBool(400, false, gc)
}
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth.
// @Produce json
// @Success 200 {object} boolResponse

View File

@ -171,6 +171,12 @@ div.card:contains(section.banner.footer) {
margin: 0.5rem;
}
p.sm,
span.sm {
font-size: 0.75rem;
}
.col.sm {
margin: .25rem;
}

View File

@ -54,6 +54,7 @@
"reset": "Reset",
"edit": "Edit",
"donate": "Donate",
"contactThrough": "Contact through:",
"extendExpiry": "Extend expiry",
"customizeMessages": "Customize Messages",
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",

View File

@ -3,9 +3,9 @@
"name": "English (US)"
},
"strings": {
"startMessage": "Hi!\nEnter your test PIN code here to verify your account.",
"languageMessage": "Note: See available languages with /lang, and set language with /lang <language code>.",
"startMessage": "Hi!\nEnter your Jellyfin PIN code here to verify your account.",
"invalidPIN": "That PIN was invalid, try again.",
"pinSuccess": "Success! You can now return to the sign-up page."
"pinSuccess": "Success! You can now return to the sign-up page.",
"languageMessage": "Note: See available languages with /lang, and set language with /lang <language code>."
}
}

View File

@ -122,14 +122,15 @@ type deleteInviteDTO struct {
}
type respUser struct {
ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user
Name string `json:"name" example:"jeff"` // Username of user
Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available)
LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
Disabled bool `json:"disabled"` // Whether or not the user is disabled.
Telegram string `json:"telegram"` // Telegram username (if known)
ID string `json:"id" example:"fdgsdfg45534fa"` // userID of user
Name string `json:"name" example:"jeff"` // Username of user
Email string `json:"email,omitempty" example:"jeff@jellyf.in"` // Email address of user (if available)
LastActive int64 `json:"last_active" example:"1617737207510"` // Time of last activity on Jellyfin
Admin bool `json:"admin" example:"false"` // Whether or not the user is Administrator
Expiry int64 `json:"expiry" example:"1617737207510"` // Expiry time of user as Epoch/Unix time.
Disabled bool `json:"disabled"` // Whether or not the user is disabled.
Telegram string `json:"telegram"` // Telegram username (if known)
NotifyThroughTelegram bool `json:"notify_telegram"`
}
type getUsersDTO struct {
@ -247,3 +248,8 @@ type telegramSetDTO struct {
Token string `json:"token" example:"A1-B2-3C"`
ID string `json:"id"` // Jellyfin ID of user.
}
type telegramNotifyDTO struct {
ID string `json:"id"`
Enabled bool `json:"enabled"`
}

View File

@ -162,6 +162,7 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.GET(p+"/telegram/pin", app.TelegramGetPin)
api.GET(p+"/telegram/verified/:pin", app.TelegramVerified)
api.POST(p+"/users/telegram", app.TelegramAddUser)
api.POST(p+"/users/telegram/notify", app.TelegramSetNotify)
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
api.GET(p+"/ombi/users", app.OmbiUsers)

8
telegram/go.mod Normal file
View File

@ -0,0 +1,8 @@
module github.com/hrfee/jfa-go/telegram
go 1.16
require (
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
)

4
telegram/go.sum Normal file
View File

@ -0,0 +1,4 @@
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/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=

59
telegram/telegram.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"fmt"
"log"
"strings"
tg "github.com/go-telegram-bot-api/telegram-bot-api"
)
const (
TOKEN = "1785754648:AAG4G6PKZpGDEJM_-MeQHJqD-xUDrrLrTC4"
USER = "johnikwock"
AUTH = "AB-CD-EF"
)
func main() {
log.Println("Connecting...")
bot, err := tg.NewBotAPI(TOKEN)
if err != nil {
log.Fatalf("Failed to initialize bot: %v", err)
}
bot.Debug = false
log.Printf("Authorized Telegram bot \"%s\"", bot.Self.UserName)
u := tg.NewUpdate(0)
u.Timeout = 60
updates, err := bot.GetUpdatesChan(u)
for update := range updates {
if update.Message == nil {
continue
}
log.Printf("New message from \"@%s\": \"%s\"", update.Message.From.UserName, update.Message.Text)
if update.Message.From.UserName != USER {
continue
}
var msg tg.MessageConfig
sects := strings.Split(update.Message.Text, " ")
if sects[0] == "/start" {
msg = tg.NewMessage(update.Message.Chat.ID, fmt.Sprintf("Enter this code on the sign-up page to continue: %s", AUTH))
} else if sects[0] != "/auth" || sects[len(sects)-1] != AUTH {
log.Println("Invalid command or auth token")
msg = tg.NewMessage(update.Message.Chat.ID, "Invalid command or token")
} else {
msg = tg.NewMessage(update.Message.Chat.ID, "Success!")
log.Println("Successful auth")
}
msg.ReplyToMessageID = update.Message.MessageID
_, err := bot.Send(msg)
if err != nil {
log.Printf("Send failed: %v", err)
}
}
}

4
telegram/telegram.txt Normal file
View File

@ -0,0 +1,4 @@
API key: 1785754648:AAG4G6PKZpGDEJM_-MeQHJqD-xUDrrLrTC4
Name: jfa-bot
Username: jfago_bot

View File

@ -12,6 +12,7 @@ interface User {
disabled: boolean;
expiry: number;
telegram: string;
notify_telegram: boolean;
}
interface getPinResponse {
@ -30,6 +31,7 @@ class user implements User {
private _emailEditButton: HTMLElement;
private _telegram: HTMLTableDataCellElement;
private _telegramUsername: string;
private _notifyTelegram: boolean;
private _expiry: HTMLTableDataCellElement;
private _expiryUnix: number;
private _lastActive: HTMLTableDataCellElement;
@ -88,10 +90,80 @@ class user implements User {
this._telegram.innerHTML = `<span class="chip btn !low">Add</span>`;
(this._telegram.querySelector("span") as HTMLSpanElement).onclick = this._addTelegram;
} else {
this._telegram.innerHTML = `<a href="https://t.me/${u}" target="_blank">@${u}</a>`;
this._telegram.innerHTML = `
<a href="https://t.me/${u}" target="_blank">@${u}</a>
<i class="icon ri-settings-2-line ml-half dropdown-button"></i>
<div class="dropdown manual">
<div class="dropdown-display">
<div class="card ~neutral !low">
<span class="supra sm">${window.lang.strings("contactThrough")}</span>
<label class="switch pb-1 mt-half">
<input type="radio" name="accounts-contact-${this.id}" class="accounts-contact-email">
<span>Email</span>
</label>
<label class="switch pb-1">
<input type="radio" name="accounts-contact-${this.id}">
<span>Telegram</span>
</label>
</div>
</div>
</div>
`;
// Javascript is necessary as including the button inside the dropdown would make it too wide to display next to the username.
const button = this._telegram.querySelector("i");
const dropdown = this._telegram.querySelector("div.dropdown") as HTMLDivElement;
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
for (let i = 0; i < radios.length; i++) {
radios[i].onclick = this._setTelegramNotify;
}
button.onclick = () => {
dropdown.classList.add("selected");
document.addEventListener("click", outerClickListener);
};
const outerClickListener = (event: Event) => {
if (!(event.target instanceof HTMLElement && (this._telegram.contains(event.target) || button.contains(event.target)))) {
dropdown.classList.remove("selected");
document.removeEventListener("click", outerClickListener);
}
};
}
}
get notify_telegram(): boolean { return this._notifyTelegram; }
set notify_telegram(s: boolean) {
if (!window.telegramEnabled || !this._telegramUsername) return;
this._notifyTelegram = s;
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
radios[0].checked = !s;
radios[1].checked = s;
}
private _setTelegramNotify = () => {
const radios = this._telegram.querySelectorAll("input") as NodeListOf<HTMLInputElement>;
let send = {
id: this.id,
enabled: radios[1].checked
};
_post("/users/telegram/notify", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status != 200) {
window.notifications.customError("errorSetTelegramNotify", window.lang.notif("errorSaveSettings"));
radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
return;
}
}
}, false, (req: XMLHttpRequest) => {
if (req.status == 0) {
window.notifications.connectionError();
radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
return;
} else if (req.status == 401) {
radios[0].checked, radios[1].checked= radios[1].checked, radios[0].checked;
window.notifications.customError("401Error", window.lang.notif("error401Unauthorized"));
}
});
}
get expiry(): number { return this._expiryUnix; }
set expiry(unix: number) {
@ -252,6 +324,7 @@ class user implements User {
this.admin = user.admin;
this.disabled = user.disabled;
this.expiry = user.expiry;
this.notify_telegram = user.notify_telegram;
}
asElement = (): HTMLTableRowElement => { return this._row; }

View File

@ -120,7 +120,7 @@ class DOMInvite implements Invite {
get usedBy(): { [name: string]: number } { return this._usedBy; }
set usedBy(uB: { [name: string]: number }) {
this._usedBy = uB;
if (uB.length == 0) {
if (Object.keys(uB).length == 0) {
this._right.classList.add("empty");
this._userTable.innerHTML = `<p class="content">${window.lang.strings("inviteNoUsersCreated")}</p>`;
return;