Compare commits

..

371 Commits

Author SHA1 Message Date
Harvey Tindall
bf12016315 accounts: make filter names translatable 2023-06-14 19:59:38 +01:00
Harvey Tindall
b544931ee5 accounts: fix id filtering, make string translatable 2023-06-14 18:57:30 +01:00
Harvey Tindall
9cef626b28 accounts: filter dropdown appears over announce one 2023-06-14 18:52:33 +01:00
Harvey Tindall
708d382a3f accounts: fix hiding of search options header for default sort 2023-06-14 18:43:46 +01:00
Harvey Tindall
f24ea4a5f8 accounts: fix sizing of sorting by button 2023-06-14 18:41:47 +01:00
Harvey Tindall
6ddd09ff1f accounts: add header to "actions" and "search options" 2023-06-14 18:38:12 +01:00
Harvey Tindall
ddc560e862 accounts: move filter button, add clear search
filter button now on left due to the dropdown being huge.
2023-06-14 17:36:41 +01:00
Harvey Tindall
6f452c62de accounts: fix search bugs, adjust top bar layout
search bar is now massive with a small filter button next to it.
Action buttons are on their own row.
Also fixed dealing with going from a search with filters in to an empty
one, search() is now called for any change at all to the input.
2023-06-14 17:15:24 +01:00
Harvey Tindall
76bb95098c accounts: add list of available filters, fix deletion of existing date filters
The "Filters" button gives a list of filterable fields, and buttons to
select the type, including true/false, text match, and on/before/after a
date. When clicked, the appropriate values are put in the search box and
the cursor is placed if any input is needed.

Dates and strings are also now matched correctly, and case-insensitively when
deleting a filter.
2023-06-14 14:01:05 +01:00
Harvey Tindall
0e241f56fb scripts: add script to generate fake accounts
might be useful for screenshots too, currently just using for testing
the sorting/filtering.
2023-06-14 11:50:22 +01:00
Harvey Tindall
8ac3bb9711 accounts: show list row of search filters, click to remove
any filters in your search box will show as little button/chip things on
the row below. Clicking them will remove them from the search.
2023-06-13 22:27:08 +01:00
Harvey Tindall
ff62f8821a accounts: filter by date, with =, <, >
Uses "any-date-parser" library to understand more date/time types.
Format is: "<field>:<equals, less than, greater than><date>", where the
part after the colon uses =, <, >. Omitting a symbol is the same as
using "=".
2023-06-13 17:19:24 +01:00
Harvey Tindall
90c433443f accounts: filter by string field, general search
string fields can now be searched by with the "<field>:<value>" syntax,
also added back a better general search, that supports essentially all
string fields, including Jellyfin ID.
2023-06-13 13:55:40 +01:00
Harvey Tindall
8a37663c89 accounts: start advanced search filter support
uses the same format "<field>:<value>", but supports quoted <values>
(allows for spaces in them), and lays groundwork to support string and
date-type field filtering. Truthiness is supported, meaning you can
check if an email is set with "email:yes" for example.
2023-06-13 13:39:13 +01:00
Harvey Tindall
bc4015ac50 accounts: add indicator of sorted column at top of list
When clicking on a column to sort by it, a button with "Sorting By:
<column>" appears. Clicking it will reset the sort, which defaults for
ascending username.
2023-06-13 12:13:12 +01:00
Harvey Tindall
f13c0d78a8 accounts: ability to sort by columns
click on column header in account table to sort by it, click again to
change sort direction. Works for all of them.
2023-06-13 11:07:56 +01:00
Harvey Tindall
cc3871adf6 site: update node deps 2023-06-12 18:49:42 +01:00
Harvey Tindall
3e52beef14 update node deps, fix resulting issues 2023-06-12 18:44:10 +01:00
Harvey Tindall
48403ce940 discord: proper support for discriminator-less
names with no discriminator are shown as @username, and search works
with the @ too. Also bumped go version to 1.20, since it was stuck on
1.16 and i felt like trying generics (in an ugly way).
2023-06-12 16:32:40 +01:00
Harvey Tindall
6564df8082 about: fix donation buttons in dark mode 2023-06-12 16:05:50 +01:00
Harvey Tindall
73202e1483 crash: render ANSI colors on crash page
Could not for the life of me get any regex to properly strip the ANSI
escape sequences, so the text log will have to suffer with them, but the
ansihtml library is now used to render them into HTML.
2023-06-12 15:48:07 +01:00
Harvey Tindall
dc60e97415 update license 2023-06-11 20:00:30 +01:00
Harvey Tindall
ad40d7d8a9 fix bugs with restarts/interrupts
The password reset daemon wasn't being closed on restarts, so an extra
pwr would be sent w/ every restart. Restarts & Interrupts (Ctrl-C)
rarely worked, as there were multiple listeners to the "RESTART"
channel, and I didn't know the message was consumed by whoever got it
first, meaning if the main thread didn't get it first, the app wouldn't
quit. Listeners are now registered, and the restart message is
re-broadcasted until everyone's got it.

Fixes #264
2023-06-11 19:50:50 +01:00
Harvey Tindall
f88f71d933 pwreset: Stop daemon on restart, dont fail if json is malformed
daemon now stops on the RESTART signal, and when it fails to read JSON,
it no longer stops the whole daemon.
2023-06-11 16:35:41 +01:00
Harvey Tindall
d688dd02c8 args: respect other args when start/stop/daemon are used
these being present in os.Args messes up "flag"'s parsing of the rest of
the arguments, so when they are found, they are removed.

Fixes #267
2023-06-11 16:06:18 +01:00
Harvey Tindall
456c99d7db args: fix help not usually showing
another weird side effect of the line cache/logging stuff.
The true stderr is now stored in the "stderr" global variable and is
used to print the help screen.
2023-06-11 15:48:27 +01:00
Harvey Tindall
e4f03fac4b setup: report panic errors to user
many issues occur with setup, all this does is tell the user something
bad happened and to check the logs. Might help with solving issues.
Also fixed some now invalid typescript.
2023-06-11 15:11:00 +01:00
Harvey Tindall
2cb72e1f48 module update, add notice to site 2023-06-11 14:22:58 +01:00
Harvey Tindall
d800b97f69 README: add note about project status 2023-04-21 18:01:08 +01:00
Harvey Tindall
0674e04ee1 go module update/tidy 2023-02-20 10:47:54 +00:00
Harvey Tindall
d56f321aad site changes 2023-02-05 01:08:29 +00:00
Harvey Tindall
bedd2bbb23 README changes 2023-02-05 00:59:04 +00:00
Harvey Tindall
27ef7ce560 css: revert global change to absolute position for dropdowns
changing dropdowns to always have absolute positioning (in c187b94)
caused issues with all other dropdowns, where neighbors were positioned
below and hidden. adding the "over-top" class to a dropdown now gives
it absolute positioning.
2023-02-05 00:49:13 +00:00
Harvey Tindall
775ebd3b1e Accounts: Unlink Telegram/Discord/Matrix through cog
Added an unlinking section to the little cog dropdown next to users so
that one can remove and re-link a different account for a Jellyfin user.
Also adjusted padding in the dropdown.
2023-02-05 00:23:16 +00:00
Harvey Tindall
49c7d83840 fix logging and crash reports on Windows
"-H=windowsgui" disables stdout on Windows, and the io.Multiwriter used
for logging had stdout as it's first entry, which failed and caused
    logging and line caching to be skipped. stdout is now removed from
    the multiwriter in this situation. Other portion of the issue was
    because crash reports had colons in their names, which Windows
    doesn't like. Fixes #168.
2023-02-02 13:42:15 +00:00
BatavianX
a30469f6ec translation from Weblate (Indonesian)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/id/
2023-02-02 14:07:20 +01:00
BatavianX
045f9ef827 Translated using Weblate (Indonesian)
Currently translated at 60.8% (14 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/id/
2023-02-02 14:07:20 +01:00
BatavianX
abc13575c9 Translated using Weblate (Indonesian)
Currently translated at 70.5% (36 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/id/
2023-02-02 14:07:20 +01:00
Farès Chati
958b824dbc translation from Weblate (French)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/fr/
2023-02-02 14:07:20 +01:00
collateral127
7b5f5abd22 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
5b7060c6a3 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
bbcba005c0 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
11d8b90f88 translation from Weblate (English (United Kingdom))
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
b531ec9b50 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
79790303a9 Translated using Weblate (English (United Kingdom))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/en_GB/
2023-02-02 14:07:20 +01:00
collateral127
11c45a67b3 translation from Weblate (English (United Kingdom))
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/en_GB/
2023-02-02 14:07:20 +01:00
ilyigna
413ea07cbd translation from Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2023-02-02 14:07:20 +01:00
Kovács Tamás
890148051f Translated using Weblate (Hungarian)
Currently translated at 16.9% (19 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/hu/
2023-02-02 14:07:20 +01:00
Kovács Tamás
3ba5d1f3fd translation from Weblate (Hungarian)
Currently translated at 95.0% (38 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/hu/
2023-02-02 14:07:20 +01:00
Kovács Tamás
ef5a0c3f75 Added translation using Weblate (Hungarian) 2023-02-02 14:07:20 +01:00
Zabpehely
0323b2783b translation from Weblate (Hungarian)
Currently translated at 32.4% (58 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/hu/
2023-02-02 14:07:20 +01:00
Linyue-GitHub
b1e8bc4a46 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/zh_Hans/
2023-02-02 14:07:20 +01:00
Linyue-GitHub
e3a33d102e translation from Weblate (Chinese (Simplified))
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/zh_Hans/
2023-02-02 14:07:20 +01:00
BIG-OP01
b9a3ed1d74 translation from Weblate (Chinese (Traditional))
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
cb0d4e8bd7 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
5ee7bdc55e Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
35ed4e20f0 Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
a5faf0699a Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
cb249b30af Translated using Weblate (Chinese (Traditional))
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
bb1e3a2c72 translation from Weblate (Chinese (Traditional))
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/zh_Hant/
2023-02-02 14:07:20 +01:00
BIG-OP01
540b8d7c13 Added translation using Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
99e524589c Added translation using Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
295343be85 Added translation using Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
6f61d2246d add translation from Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
e0cd4ed379 Added translation using Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
9f3269bce7 Added translation using Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
BIG-OP01
464fabc3bb add translation from Weblate (Chinese (Traditional)) 2023-02-02 14:07:20 +01:00
Harvey Tindall
c187b948d9 accounts: display "Contact through" above container
stops most of it being hidden at the bottom of the page.
2023-02-02 12:18:34 +00:00
Harvey Tindall
eb85ee4d35 add option to auto redirect to jellyfin (or given link)
if enabled (General>Auto redirect on success), the user will not have to
    click "continue" on the form or creation success page and will
    insted be redirected. For #242.
2023-02-02 10:34:22 +00:00
Harvey Tindall
de6bc02ad8 Merge branch 'main' of github.com:hrfee/jfa-go 2023-02-02 09:41:56 +00:00
Harvey Tindall
d355b3167f fix GOESBUILD for newer go versions
switched to 'go install' syntax. for #248.
2023-02-02 09:41:14 +00:00
Harvey Tindall
6cd846dfd8 Merge pull request #251 from aleksasiriski/patch-1
fixed setExpiry() and added checked to admin only
2023-02-02 09:27:12 +00:00
Aleksa Siriški
0c102f5324 added checked to admin only
because it is confusing if you go straight to `Manually set the username and password.` and you can't go next since nothing is checked above but it becomes hidden
2023-02-01 23:31:55 +01:00
Aleksa Siriški
f50596c4a1 fixed setExpiry()
The bug was that setExpiry didn't work if account was enabled and didn't have an expiry already.
2023-02-01 23:11:21 +01:00
Harvey Tindall
5d289ce023 Admin: auto enable contact when an email is added
"Contact through email" is now automatically enabled when adding an
email address to an account without one on the admin page. Solves #233.
2023-02-01 15:25:47 +00:00
Harvey Tindall
2722e8482d Invites: unique email/ID requirement
"Require unique ..." Settings (`require_unique` in relevant sections of
config.ini) are now available for email/discord/telegram/matrix. An
error is shown on the invite form if a non-unique address/ID is used.
This was on my kanban without a link to an issue, so i'm guessing it was
requested on Discord.
2023-02-01 15:11:10 +00:00
Harvey Tindall
895dcf5a30 fix missing create-success css
part of #242
2023-02-01 14:14:13 +00:00
Harvey Tindall
ac25c9cd7f accounts: Show "contact through" cog when only email is given"
one portion of #233: When adding an email address to an account, the
admin (for now) will manually have to enable its use by clicking the cog icon and
enabling "contact through email". Only for now, currently some other
unfinished work is stopping me from committing the other portion of code
for this issue.
2023-01-30 14:00:29 +00:00
Harvey Tindall
47d00d1f27 site: properly fix modals
uncss was clearing out the "block" and "animate-fade-in/out", so removed
that step since tailwind does a similar thing anyway.
2022-12-29 21:15:07 +00:00
Harvey Tindall
6bab805528 site: fix modals 2022-12-29 20:31:29 +00:00
Harvey Tindall
6efd28d904 fix translation conflict 2022-12-29 17:48:37 +00:00
Harvey Tindall
04329bf171 fix issue with light mode colors 2022-12-29 17:32:59 +00:00
Harvey Tindall
3d56b6864e Merge pull request #234 from HackingHackers/main
Fix Chinese translation issues
2022-12-29 16:51:25 +00:00
undy
e2e675e469 Fix Chinese translation errors 2022-11-30 15:45:45 -05:00
alison2033
aceb98b4a0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/pt_BR/
2022-11-03 02:49:12 +01:00
alison2033
b848faa2c0 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/pt_BR/
2022-11-03 02:49:12 +01:00
alison2033
ea04f5391e Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/pt_BR/
2022-11-03 02:49:12 +01:00
alison2033
58e61e514a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/pt_BR/
2022-11-03 02:49:12 +01:00
alison2033
b91918b04d translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/pt_BR/
2022-11-03 02:49:11 +01:00
alison2033
8032fa0bcc Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/pt_BR/
2022-11-03 02:49:11 +01:00
Mateusz Tasz
1f0c641610 Translated using Weblate (Polish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/pl/
2022-09-23 12:49:01 +02:00
Mateusz Tasz
37fa9345cf Translated using Weblate (Polish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/pl/
2022-09-23 12:49:01 +02:00
Mateusz Tasz
2c31032a1c Translated using Weblate (Polish)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/pl/
2022-09-23 12:49:01 +02:00
Mateusz Tasz
aeb85486c4 Translated using Weblate (Polish)
Currently translated at 42.8% (48 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/pl/
2022-09-23 12:49:00 +02:00
Mateusz Tasz
4f5fe6723b Translated using Weblate (Polish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/pl/
2022-09-23 12:49:00 +02:00
Mateusz Tasz
53a8e6df51 translation from Weblate (Polish)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/pl/
2022-09-23 12:49:00 +02:00
Mateusz Tasz
f45409e456 translation from Weblate (Polish)
Currently translated at 30.7% (55 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/pl/
2022-09-23 12:48:59 +02:00
Mateusz Tasz
34df600350 Added translation using Weblate (Polish) 2022-09-22 12:47:16 +02:00
Mateusz Tasz
255640a385 add translation from Weblate (Polish) 2022-09-22 12:38:48 +02:00
Mateusz Tasz
442bcf7e4f Added translation using Weblate (Polish) 2022-09-22 12:34:37 +02:00
Mateusz Tasz
3a8540a439 Added translation using Weblate (Polish) 2022-09-22 12:19:36 +02:00
Mateusz Tasz
681038cbd4 Added translation using Weblate (Polish) 2022-09-22 12:13:24 +02:00
Mateusz Tasz
bb8c450452 Added translation using Weblate (Polish) 2022-09-22 12:08:02 +02:00
Mateusz Tasz
5e41de8edd add translation from Weblate (Polish) 2022-09-22 11:52:57 +02:00
josecbail
47f7987210 Translated using Weblate (Spanish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/es/
2022-08-31 06:48:52 +02:00
josecbail
3515aee8e8 translation from Weblate (Spanish)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2022-08-31 06:48:52 +02:00
josecbail
23223f3925 Translated using Weblate (Spanish)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/es/
2022-08-31 06:48:52 +02:00
josecbail
f049973349 translation from Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2022-08-31 06:48:51 +02:00
josecbail
2cdef91d11 Translated using Weblate (Spanish)
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/es/
2022-08-31 06:48:51 +02:00
MiGeek
297ec33e8e translation from Weblate (Spanish)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2022-07-21 22:48:39 +02:00
kuesttman
dc55959df4 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/pt_BR/
2022-07-21 22:48:38 +02:00
joecom7
311b64acd1 Translated using Weblate (Italian)
Currently translated at 10.0% (1 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/it/
2022-07-02 13:48:34 +02:00
joecom7
89f11ab630 Translated using Weblate (Italian)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/it/
2022-07-02 13:48:33 +02:00
joecom7
9c68a7970d Translated using Weblate (Italian)
Currently translated at 39.2% (20 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/it/
2022-07-02 13:48:33 +02:00
joecom7
18d619efa1 translation from Weblate (Italian)
Currently translated at 77.5% (31 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/it/
2022-07-02 13:48:33 +02:00
joecom7
6490c67a6c Added translation using Weblate (Italian) 2022-07-01 13:30:14 +02:00
joecom7
8cdf87d72b Added translation using Weblate (Italian) 2022-07-01 13:20:10 +02:00
tenninjas
46da6d0ddc translation from Weblate (Chinese (Simplified))
Currently translated at 95.5% (171 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/zh_Hans/
2022-04-26 18:46:35 +02:00
tenninjas
89b9f0a4f9 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/zh_Hans/
2022-04-26 18:46:34 +02:00
tenninjas
887f1f7c71 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/zh_Hans/
2022-04-26 18:46:34 +02:00
tenninjas
c1f7b665d5 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/zh_Hans/
2022-04-26 18:46:34 +02:00
tenninjas
26fc6b7056 translation from Weblate (Chinese (Simplified))
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/zh_Hans/
2022-04-26 18:46:34 +02:00
tobycm
3d45db2606 translation from Weblate (Vietnamese)
Currently translated at 35.0% (14 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/vi/
2022-04-16 19:48:07 +02:00
tobycm
91603945ef translation from Weblate (Vietnamese)
Currently translated at 70.3% (126 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/vi/
2022-04-16 19:48:07 +02:00
Felix Neumann
d6df3b980c Translated using Weblate (Chinese (Simplified))
Currently translated at 94.6% (106 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/zh_Hans/
2022-04-16 19:48:07 +02:00
Felix Neumann
d1185d0f5f Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/zh_Hans/
2022-04-16 19:48:06 +02:00
ZakiZtraki
f35132e182 Translated using Weblate (German)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/de/
2022-04-16 19:48:06 +02:00
ZakiZtraki
09d22a9f2d translation from Weblate (German)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/de/
2022-04-16 19:48:06 +02:00
tobycm
b0ee05f07d add translation from Weblate (Vietnamese) 2022-04-15 18:57:40 +02:00
tobycm
bb33c11a6b translation from Weblate (Vietnamese)
Currently translated at 70.3% (126 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/vi/
2022-04-15 06:48:05 +02:00
3ole
728152a31c Translated using Weblate (German)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/de/
2022-04-11 21:48:05 +02:00
DragoPrime
048f4bdf90 Translated using Weblate (Romanian)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/ro/
2022-04-06 20:48:08 +02:00
DragoPrime
8c405b251f Translated using Weblate (Romanian)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/ro/
2022-04-06 20:48:08 +02:00
DragoPrime
53ba09a2fe Translated using Weblate (Romanian)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/ro/
2022-04-06 20:48:07 +02:00
DragoPrime
0d62c5ecfa translation from Weblate (Romanian)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/ro/
2022-04-06 20:48:07 +02:00
DragoPrime
44bb1e6803 Added translation using Weblate (Romanian) 2022-04-05 20:48:33 +02:00
DragoPrime
6f69f3b8f5 Added translation using Weblate (Romanian) 2022-04-05 20:34:52 +02:00
DragoPrime
d97576678d Added translation using Weblate (Romanian) 2022-04-05 20:32:13 +02:00
DragoPrime
88bf4f9903 add translation from Weblate (Romanian) 2022-04-05 20:16:05 +02:00
theGUI001
f07227e560 translation from Weblate (Portuguese (Brazil))
Currently translated at 92.5% (37 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/pt_BR/
2022-04-04 00:48:02 +02:00
mLgz0rn
b197c678ef translation from Weblate (Danish)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/da/
2022-03-26 10:47:59 +01:00
mLgz0rn
d13981b489 Translated using Weblate (Danish)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/da/
2022-03-26 10:47:59 +01:00
mLgz0rn
90d4681ae8 Translated using Weblate (Danish)
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/da/
2022-03-26 10:47:59 +01:00
mLgz0rn
ce228630ce Translated using Weblate (Danish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/da/
2022-03-26 10:47:59 +01:00
mLgz0rn
855fdee332 translation from Weblate (Danish)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/da/
2022-03-26 10:47:59 +01:00
3ole
f8745636f2 Translated using Weblate (German)
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/de/
2022-03-26 10:47:58 +01:00
Richard de Boer
aa07ff1682 Translated using Weblate (Dutch)
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/nl/
2022-03-24 10:48:03 +01:00
Harvey Tindall
4c3e310634 use go install for swag in goreleaser 2022-03-22 15:16:04 +00:00
Harvey Tindall
8b52330304 remove unused nightwind dep 2022-03-22 15:09:38 +00:00
Harvey Tindall
200dc1c91a lowercase lang 2022-03-22 15:03:53 +00:00
3ole
531d4aaefc Translated using Weblate (German)
Currently translated at 100.0% (111 of 111 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/de/
2022-03-22 16:01:21 +01:00
3ole
f8d98fb66f Translated using Weblate (German)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/de/
2022-03-22 16:01:21 +01:00
3ole
5b2472853b translation from Weblate (German)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/de/
2022-03-22 16:01:21 +01:00
mossh
2dc234a94d Translated using Weblate (Arabic)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/ar/
2022-03-22 16:01:21 +01:00
mossh
fdb43b2c53 translation from Weblate (Arabic)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/ar/
2022-03-22 16:01:21 +01:00
mossh
2161d1aa2f Translated using Weblate (Arabic)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/ar/
2022-03-22 16:01:21 +01:00
mossh
08c8534d39 Added translation using Weblate (Arabic) 2022-03-22 16:01:21 +01:00
mossh
9e7914e0b6 add translation from Weblate (Arabic) 2022-03-22 16:01:21 +01:00
mossh
daebfec0a2 Added translation using Weblate (Arabic) 2022-03-22 16:01:21 +01:00
3ole
e81411cb9b Translated using Weblate (German)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/de/
2022-03-22 16:01:21 +01:00
3ole
6cdae16752 Translated using Weblate (German)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/de/
2022-03-22 16:01:21 +01:00
3ole
65a0c6cb23 Translated using Weblate (German)
Currently translated at 100.0% (111 of 111 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/de/
2022-03-22 16:01:21 +01:00
3ole
36d0550bf9 Translated using Weblate (German)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/de/
2022-03-22 16:01:20 +01:00
3ole
92aee997da Translated using Weblate (German)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/de/
2022-03-22 16:01:20 +01:00
3ole
63e6f3a3fa translation from Weblate (German)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/de/
2022-03-22 16:01:20 +01:00
Tim
bfafbad9dc translation from Weblate (German)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/de/
2022-03-22 16:01:20 +01:00
Sundune
4435fead5a translation from Weblate (German)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/de/
2022-03-22 16:01:20 +01:00
3ole
e73715f30e translation from Weblate (German)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/de/
2022-03-22 16:01:20 +01:00
Herodev
f31e9c0d81 translation from Weblate (German)
Currently translated at 95.0% (38 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/de/
2022-03-22 16:01:20 +01:00
DornJ
2d1a4737db Translated using Weblate (Slovenian)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/sl/
2022-03-22 16:01:20 +01:00
DornJ
91f89793da Translated using Weblate (Slovenian)
Currently translated at 0.9% (1 of 111 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/sl/
2022-03-22 16:01:20 +01:00
DornJ
d201644a5b Translated using Weblate (Slovenian)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/sl/
2022-03-22 16:01:20 +01:00
DornJ
982dd001ef Translated using Weblate (Slovenian)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/sl/
2022-03-22 16:01:20 +01:00
DornJ
80bd4d134e translation from Weblate (Slovenian)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/sl/
2022-03-22 16:01:20 +01:00
DornJ
92fda348cd Added translation using Weblate (Slovenian) 2022-03-22 16:01:20 +01:00
DornJ
ef3fdb7555 Added translation using Weblate (Slovenian) 2022-03-22 16:01:20 +01:00
DornJ
9eb30ffab3 Added translation using Weblate (Slovenian) 2022-03-22 16:01:20 +01:00
DornJ
0bf6c25a14 Added translation using Weblate (Slovenian) 2022-03-22 16:01:20 +01:00
DornJ
5bb7a7d30c add translation from Weblate (Slovenian) 2022-03-22 16:01:20 +01:00
Richard de Boer
85c9319dfd Translated using Weblate (Dutch)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/nl/
2022-03-22 16:01:20 +01:00
Richard de Boer
3fea051691 Translated using Weblate (Dutch)
Currently translated at 100.0% (111 of 111 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/nl/
2022-03-22 16:01:20 +01:00
Richard de Boer
7826fdffeb Translated using Weblate (Dutch)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/nl/
2022-03-22 16:01:20 +01:00
Richard de Boer
1105605370 translation from Weblate (Dutch)
Currently translated at 100.0% (40 of 40 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/nl/
2022-03-22 16:01:20 +01:00
Richard de Boer
3de3dd426b translation from Weblate (Dutch)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/nl/
2022-03-22 16:01:20 +01:00
Richard de Boer
0c7187d53f translation from Weblate (English)
Currently translated at 100.0% (179 of 179 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/en/
2022-03-22 16:01:20 +01:00
Harvey Tindall
12db53d1eb reorganise api, add "Connection refused" error in setup
fixes #201
2022-03-22 14:58:39 +00:00
Harvey Tindall
cebde9d4c0 setup: report errors when saving config 2022-02-03 19:19:21 +00:00
Harvey Tindall
9395165916 updater: check build is ready 2022-01-30 18:25:47 +00:00
Harvey Tindall
a8daa2c77e makefile changes 2022-01-30 18:03:21 +00:00
Harvey Tindall
49c873c858 dont ignore esbuild errors 2022-01-30 17:59:52 +00:00
Harvey Tindall
c6fc5765f3 update node version in Dockerfile 2022-01-30 17:18:51 +00:00
Harvey Tindall
62cbbf57e7 form: fix mono text in discord linking modal 2022-01-30 16:47:29 +00:00
Harvey Tindall
b81c5636cc settings: better top button padding on mobile 2022-01-30 16:43:28 +00:00
Harvey Tindall
d867649a93 padding; fix hungarian lang names 2022-01-30 16:38:19 +00:00
Harvey Tindall
cd08259012 lowercase lang 2022-01-30 16:28:20 +00:00
Harvey Tindall
e814af1af5 Merge pull request #191 from LubricantJam/main
Further Mobile Optimisations
2022-01-30 16:27:33 +00:00
Harvey Tindall
ecbff16a88 modal: change transition
now a simple fade-in/fade-out, which is part of tailwind.config.js
rather than modal.css.
2022-01-30 16:24:40 +00:00
Harvey Tindall
baffa4a38c add NOTEMPLATE env var to missing-colors.js 2022-01-30 14:41:11 +00:00
mLgz0rn
fad507d2dd translation from Weblate (Danish)
Currently translated at 100.0% (175 of 175 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/da/
2022-01-30 15:33:52 +01:00
mLgz0rn
053ee8284d Translated using Weblate (Danish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/da/
2022-01-30 15:33:52 +01:00
mLgz0rn
4f05fa9375 Translated using Weblate (Danish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/da/
2022-01-30 15:33:52 +01:00
mLgz0rn
7ecf1bcf94 Translated using Weblate (Danish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/da/
2022-01-30 15:33:52 +01:00
mLgz0rn
f030cdcb02 translation from Weblate (Danish)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/da/
2022-01-30 15:33:52 +01:00
Frizles
7474a1868e translation from Weblate (Hungarian)
Currently translated at 86.4% (32 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/hu/
2022-01-30 15:33:52 +01:00
Tim
9a4d90790a translation from Weblate (German)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/de/
2022-01-30 15:33:52 +01:00
Frizles
7e38ee07c7 add translation from Weblate (Hungarian) 2022-01-30 15:33:52 +01:00
Frizles
b9574e2d67 add translation from Weblate (Hungarian) 2022-01-30 15:33:52 +01:00
Harvey Tindall
6431613363 update version; mention log button in bug report template 2022-01-30 14:27:00 +00:00
Harvey Tindall
a00392166c Merge pull request #194 from KeyboardDabbler/main
Cursor pointer UX consistency
2022-01-30 14:10:37 +00:00
KeyboardDabbler
bfcad6c5f2 pointer on modal close 2022-01-30 01:49:44 +13:00
KeyboardDabbler
7daf2162ef checkbox, edit email and captcha refresh 2022-01-30 00:33:25 +13:00
James Finch
dec8d75083 Optimised modals for mobile. 2022-01-28 22:34:34 +00:00
James Finch
f486f8de1d Removed deprecated code. 2022-01-28 22:33:39 +00:00
James Finch
93b1e9c371 Fixed dark text on dark mode. 2022-01-28 22:33:30 +00:00
James Finch
6440f57467 Fixed Tailwind compiling issues. 2022-01-28 22:33:07 +00:00
Harvey Tindall
25451eb763 form: show radios for only linked contact methods 2022-01-27 16:56:21 +00:00
Harvey Tindall
dbefb80f63 form: reliably disable submit button, communicate if account linking is required 2022-01-27 16:48:46 +00:00
James Finch
889d68dc7b Updated Tailwind version. 2022-01-27 16:32:01 +00:00
Harvey Tindall
42dbc04ff9 Merge pull request #189 from LubricantJam/main
Mobile Form Support.
2022-01-27 15:01:30 +00:00
James Finch
82c8ef1e4b Added better mobile support for sign-up form. 2022-01-27 01:58:24 +00:00
Harvey Tindall
4deb45df3c fix code text color in dark mode 2022-01-26 22:25:33 +00:00
Harvey Tindall
4b02960fd1 form: add captcha regen button
lots of false negatives so this is a solution for now.

Also the registering of each discord command is now logged for debugging
purposes.
2022-01-26 22:14:16 +00:00
Harvey Tindall
15e5564b12 discord: add/move to slash commands
the version of the discord library with support for this isn't
necessarily stable, so normal ! commands will still be available. The
user is no longer DMed for the PIN, instead they type /pin <PIN>.
2022-01-26 21:47:02 +00:00
Harvey Tindall
e66241ddcb fix crash page css when using goreleaser 2022-01-26 15:45:21 +00:00
Harvey Tindall
d3990a6c55 upgrade vulnerable dependency 2022-01-26 14:37:14 +00:00
Harvey Tindall
be1d081629 build: fix css bundling bug with new esbuild
local testing was being done with an older version of esbuild which
didn't mind @tailwind statements before @imports (it complained, but did
its job). On the latest version used in Docker builds, it would leave
the @import statements intact which broke things like modals.
2022-01-26 14:26:10 +00:00
Harvey Tindall
fafb524a47 accounts: link ombi/discord when adding 2022-01-25 18:05:17 +00:00
Harvey Tindall
da1b9ccac7 ombi: add migration to link telegram/discord 2022-01-25 18:02:04 +00:00
Harvey Tindall
7b97e1ca26 fix discord/telegram linking with ombi
original issue creator replied with the fix.
2022-01-25 17:37:14 +00:00
Harvey Tindall
7f11549337 ombi: broken discord/telegram linking
doesn't work due to https://github.com/ombi-app/ombi/issues/3484
committing to save progress.
2022-01-25 17:01:18 +00:00
Harvey Tindall
987e0ddd4e accounts: show "Set expiry" button for non-disabled users
previously hidden in a dropdown on the "re-enable" button for disabled
users only, now can be used on active ones.
2022-01-25 15:27:16 +00:00
Harvey Tindall
8fd097836f discord: send in-channel reply to !start 2022-01-14 17:26:19 +00:00
Harvey Tindall
5acee68987 settings: rename URL base to reverse proxy subfolder 2022-01-14 17:04:17 +00:00
Harvey Tindall
25a1ca5a9f bots: fix language selection
you may have to re-apply your language choice.
2022-01-14 17:01:42 +00:00
Harvey Tindall
32af107699 discord: add role to user on signup
This requires an extra permission, so you'll have to modify your bot
settings, kick it from your server and re-add it for this to work. The
role you select must also be lower in hierarchy than the bot's group.
2022-01-13 22:34:52 +00:00
Harvey Tindall
b929e16f2c build: apply cssVersion for all ldflags 2022-01-13 21:49:56 +00:00
Harvey Tindall
1c942186aa form: fix captcha
wouldn't compile (not sure why i didn't notice) and after fixing, the
check was being performed after deleting the invite so would always
fail.
2022-01-13 20:40:58 +00:00
Harvey Tindall
d9f8785372 form: add CAPTCHAs
Enabled in Settings > Captchas, shows a captcha on the account creation
form.
2022-01-10 01:55:48 +00:00
Harvey Tindall
8758d74e32 fix titles for some pages 2022-01-10 00:46:01 +00:00
Harvey Tindall
6448a7db9e accounts: allow giving individual users jfa-go access
New "Access jfa-go" column allows you to select users for jfa-go access.
New "Allow All" setting allows all Jellyfin users access, as disabling
"Admin Only" no longer does this.
2022-01-09 19:37:17 +00:00
Richard de Boer
46d1da7cd3 translation from Weblate (Dutch)
Currently translated at 100.0% (175 of 175 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/nl/
2022-01-08 17:43:45 +01:00
Harvey Tindall
77c05a4d4f prefix css with version to avoid cache conflict 2022-01-08 16:42:36 +00:00
Harvey Tindall
4024334c0c accounts: add ability to label users
press the pen icon next to their username to add a nick-name to remind
you who they are.
2022-01-08 16:20:31 +00:00
Harvey Tindall
3294b27029 add replaceAll polyfill 2022-01-08 00:22:21 +00:00
Harvey Tindall
0d4747e8e9 Merge branch 'main' of github.com:hrfee/jfa-go 2022-01-08 00:09:35 +00:00
Harvey Tindall
1ebc648158 fix broken go template if statements
All available DOM parsers for node would move the contents of if
statements outside of them, breaking things like the accounts tab. Fixed
with a regex pre and post process to comment out then uncomment all template usage.

builds now depend on perl for some regex, this can likely be changed in
future though.
2022-01-08 00:07:23 +00:00
mLgz0rn
7e5bfd4b10 translation from Weblate (Danish)
Currently translated at 94.2% (164 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
5074f4d7af Translated using Weblate (Danish)
Currently translated at 90.0% (9 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
22feec49cb Translated using Weblate (Danish)
Currently translated at 100.0% (21 of 21 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
4b59876d8b Translated using Weblate (Danish)
Currently translated at 100.0% (109 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
3c278bc930 Translated using Weblate (Danish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
0221d29ecb Translated using Weblate (Danish)
Currently translated at 96.0% (49 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/da/
2022-01-05 04:57:53 +01:00
mLgz0rn
130b56f02d translation from Weblate (Danish)
Currently translated at 97.2% (36 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/da/
2022-01-05 04:57:53 +01:00
Harvey Tindall
e86f5f4c3c site: use tailwind 2022-01-04 21:14:12 +00:00
Harvey Tindall
3b0701e772 fix dark mode script 2022-01-04 20:46:22 +00:00
Harvey Tindall
9874dce520 merge tailwind and upgraded a17t
a17t v0.10 became a tailwind plugin rather than standalone css, and made
some other changes. Much of the original custom CSS now uses tailwind
classes, and there have been some other UI changes.
2022-01-04 20:28:36 +00:00
Harvey Tindall
2f50ab36fd pad table and some other stuff 2022-01-04 20:09:51 +00:00
Harvey Tindall
6124b9b3f3 switch accounts tab table to white bg 2022-01-04 19:22:49 +00:00
Harvey Tindall
7ff492df6c site: allow html injection 2022-01-02 22:05:26 +00:00
Harvey Tindall
a8ce35c13f fixed height on table rows 2022-01-01 03:29:09 +00:00
Harvey Tindall
26d9864051 fix layout on remaining html 2022-01-01 02:58:26 +00:00
Harvey Tindall
a3a22d353c lang: make loadLangSelector respect existing params 2022-01-01 02:54:02 +00:00
Harvey Tindall
dd5eecf9f9 fix textareas 2022-01-01 02:17:24 +00:00
Harvey Tindall
7e0e0b0520 fix form layout 2021-12-31 18:52:03 +00:00
Harvey Tindall
8bee09cd01 fix settings button highlight 2021-12-31 18:30:15 +00:00
Harvey Tindall
deb117fc34 fix setup layout 2021-12-31 18:00:19 +00:00
Harvey Tindall
a9a0005007 setup: patch messages with english fallback 2021-12-31 17:49:25 +00:00
Harvey Tindall
4eb7afead6 fix banner in about 2021-12-31 17:43:09 +00:00
Harvey Tindall
d1b5b74060 make most modals white 2021-12-31 17:28:08 +00:00
Harvey Tindall
cf91ee62ed change invite color in light mode 2021-12-31 16:07:21 +00:00
Harvey Tindall
277690ef79 allow for defining custom dark: variants, ignore querySelector, shrink margins 2021-12-31 16:01:17 +00:00
Harvey Tindall
f7f3530a33 fix crash page 2021-12-31 15:09:33 +00:00
Harvey Tindall
2d3a5c739c fix about page 2021-12-31 02:22:23 +00:00
Harvey Tindall
3dbb993d35 fix button layout on accounts tab 2021-12-31 02:03:29 +00:00
Harvey Tindall
508168b49e fix more oddities 2021-12-31 01:51:42 +00:00
Harvey Tindall
0e1cbd7e7b fix m-half in ts 2021-12-31 00:26:11 +00:00
Harvey Tindall
e73ecb7a52 fix some padding issues 2021-12-31 00:22:28 +00:00
Harvey Tindall
62be8adc65 remove predefined m- and p- spacing 2021-12-30 23:58:20 +00:00
Harvey Tindall
acc8892f26 switch to DOM based variant adding 2021-12-30 23:52:53 +00:00
Harvey Tindall
51b59ae103 settings: add button to get logs 2021-12-30 17:54:27 +00:00
Harvey Tindall
8888807780 remove debug print, add another 2021-12-30 17:30:29 +00:00
Harvey Tindall
0f0355fd01 fix inline 2021-12-30 02:59:44 +00:00
Harvey Tindall
d19f7d6b53 mailgun: better handle different api url formats 2021-12-30 02:55:00 +00:00
Harvey Tindall
a31f174375 add dark variants to ts
janky but works, and should report if theres a situation its not ready
to handle.
2021-12-30 02:45:29 +00:00
Harvey Tindall
18ae03554f tailwind: upgrade a17t, somewhat functional dark mode
instead of adding dark: variants to each element, a preprocessor script
adds them. still needs to be implemented to typescript.
2021-12-30 00:49:43 +00:00
Harvey Tindall
07de4e5015 build: include systemd service 2021-12-29 23:29:34 +00:00
Harvey Tindall
57e6469564 site: add direct links to unstable builds
Links to build types and architectures are now included in the unstable
download section.
2021-12-29 22:33:07 +00:00
roand0617
cd2c37057d Translated using Weblate (Spanish)
Currently translated at 100.0% (109 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/es/
2021-12-28 00:56:15 +01:00
roand0617
a35ca762e3 Translated using Weblate (Spanish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/es/
2021-12-26 01:56:16 +01:00
roand0617
fd10b2600f Translated using Weblate (Spanish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/es/
2021-12-26 01:56:16 +01:00
roand0617
1384091d95 Translated using Weblate (Spanish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/es/
2021-12-26 01:56:16 +01:00
roand0617
ca29ea2d46 translation from Weblate (Spanish)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2021-12-26 01:56:16 +01:00
roand0617
d8c9ae4ff6 Translated using Weblate (Spanish)
Currently translated at 100.0% (21 of 21 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/es/
2021-12-26 01:56:15 +01:00
roand0617
4403ea8e18 translation from Weblate (Spanish)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2021-12-26 01:56:15 +01:00
Richard de Boer
528829ffda translation from Weblate (Dutch)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/nl/
2021-12-26 01:56:15 +01:00
roand0617
84429a3399 Translated using Weblate (Spanish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/es/
2021-12-25 00:17:09 +01:00
roand0617
6be5d6cbcb Translated using Weblate (Spanish)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/es/
2021-12-25 00:17:09 +01:00
roand0617
c59ea2000c Translated using Weblate (Spanish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/es/
2021-12-25 00:17:09 +01:00
roand0617
30ee554f56 translation from Weblate (Spanish)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2021-12-25 00:17:09 +01:00
roand0617
c1d984b86d Translated using Weblate (Spanish)
Currently translated at 100.0% (21 of 21 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/es/
2021-12-25 00:17:09 +01:00
roand0617
fe1570d0bc translation from Weblate (Spanish)
Currently translated at 100.0% (36 of 36 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2021-12-25 00:17:09 +01:00
roand0617
edfd295fb4 Translated using Weblate (Spanish)
Currently translated at 100.0% (109 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/es/
2021-12-25 00:17:09 +01:00
xarmadigi
d57d33b620 translation from Weblate (Spanish)
Currently translated at 94.8% (165 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2021-12-25 00:17:09 +01:00
Harvey Tindall
aedea1bea6 admin: move theme icon to top left; add icon 2021-12-24 19:34:18 +00:00
Harvey Tindall
535a100314 settings: discord and more links in "About" 2021-12-24 19:22:35 +00:00
Harvey Tindall
360c25d084 accounts: hide "Send PWR" when link resets disabled
for #182.
2021-12-24 19:05:48 +00:00
Harvey Tindall
d47afe05f4 update deps, fix connection error log 2021-12-22 23:11:00 +00:00
Harvey Tindall
942792cdfa add discord link to site 2021-12-21 14:04:24 +00:00
Harvey Tindall
685254950e add discord link; new thumbnail variant 2021-12-21 13:43:37 +00:00
Harvey Tindall
e6cc7fce1a form: add setting for changing redirect url
for #167, Settings > General (Advanced) > Form success redirect URL.
2021-12-20 20:44:08 +00:00
Harvey Tindall
d8b1f03ac4 form: substitute Jellyfin on success messages
fixes #177
2021-12-20 20:17:18 +00:00
Harvey Tindall
d81679fbae print error if logging fails 2021-12-20 19:05:18 +00:00
Harvey Tindall
ebb49fce97 fix FAQ link in bug report template 2021-12-20 18:52:06 +00:00
Harvey Tindall
0fd4f516b1 email: Add option to require on sign-up
for #172
2021-12-20 18:50:48 +00:00
Harvey Tindall
9fff5781f4 lowercase lang 2021-12-20 17:58:34 +00:00
hongphuctran77
e19352a69f translation from Weblate (Vietnamese)
Currently translated at 71.2% (124 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/vi/
2021-12-11 09:55:10 +01:00
hongphuctran77
09b96e5983 add translation from Weblate (Vietnamese) 2021-12-09 13:29:46 +01:00
Richard de Boer
bd9f4258e2 Translated using Weblate (Dutch)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/nl/
2021-11-22 16:55:06 +01:00
Etienne dP
a37cdf43f3 Translated using Weblate (French)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/fr/
2021-11-18 23:55:06 +01:00
Etienne dP
4821b30634 translation from Weblate (French)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/fr/
2021-11-18 23:55:05 +01:00
Richard de Boer
dd8dfcb2b1 translation from Weblate (Dutch)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/nl/
2021-11-17 17:55:26 +01:00
Harvey Tindall
73c7f22bd1 messages: add option to show/hide linking on registration
In each of the Discord/Telegram/Matrix sections, the "Show on user
registration" option can be disabled to hide the "Link xxx" button on
the registration form. This is useful is you're only using these
registrations for admin purposes.
2021-11-17 16:49:26 +00:00
Harvey Tindall
e7ca335d83 invites: allow notification by discord/telegram/matrix
also added migration as this required changing the indexing of notify
preferences from email addresses to Jellyfin IDs.
2021-11-17 16:20:57 +00:00
Harvey Tindall
3730775018 site: put subheading and features on separate line 2021-11-15 15:18:48 +00:00
Harvey Tindall
4fcba32f74 apidocs: re-version, set up for hosted ui
swagger.json is uploaded after each build, and is hosted at
api.jfa-go.com.
2021-11-15 00:19:52 +00:00
Harvey Tindall
b39ad5c688 site: add features, dono button 2021-11-14 23:06:15 +00:00
Harvey Tindall
a41b382dba merge translation 2021-11-14 14:58:28 +00:00
Harvey Tindall
9092f42834 remove vulnerable node deps, cleanup 2021-11-14 14:50:40 +00:00
thomasl78
af563aa6e5 Translated using Weblate (French)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/fr/
2021-11-14 15:14:25 +01:00
thomasl78
f37edcb751 translation from Weblate (French)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/fr/
2021-11-14 15:14:25 +01:00
thomasl78
5d33dcf68e translation from Weblate (French)
Currently translated at 98.8% (172 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/fr/
2021-11-14 15:14:25 +01:00
Richard de Boer
947da02b3c Translated using Weblate (Dutch)
Currently translated at 100.0% (10 of 10 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/nl/
2021-11-14 15:14:25 +01:00
Richard de Boer
838d108d25 translation from Weblate (Dutch)
Currently translated at 100.0% (36 of 36 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/nl/
2021-11-14 15:14:25 +01:00
Richard de Boer
4a19af3353 translation from Weblate (Dutch)
Currently translated at 100.0% (174 of 174 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/nl/
2021-11-14 15:14:25 +01:00
Harvey Tindall
4da1c8c2b6 Ombi: Integrate with profiles, Retire user defaults
NOTE: If you previously used the Ombi integration, New ombi users
won't be created until you set this up.

Ombi settings can be added to a profile in Settings > User Profiles.
The "Modify settings" options will now apply to ombi if the selected
profile has ombi settings.
2021-11-13 18:53:53 +00:00
Harvey Tindall
c988239fa8 accounts: allow yes/no in search 2021-11-13 17:00:18 +00:00
Harvey Tindall
94e3c13b3e Users: Add delay when modifying >100 users
Hopefully avoids Jellyfin crashing. For #160
2021-11-10 20:45:39 +00:00
Harvey Tindall
36f3860c4c SMTP: Always use plaintext as main body
Adding the HTML as the alternative body caused the plaintext email to
always appear. Should fix #164.
2021-11-10 20:12:31 +00:00
Harvey Tindall
f78fa28822 announcements: fix preview window 2021-11-10 20:04:04 +00:00
Harvey Tindall
2de7182c55 Merge from SquaredPotato/smtp-certificate-validation
Adds "Verify certificate" in Settings > SMTP to disable SSL certificate validation, useful for local servers or relays.
2021-11-10 19:42:08 +00:00
Harvey Tindall
f3e1606440 add sponsors to readme 2021-11-10 19:26:39 +00:00
Stefan Schokker
b7236319ec Actually use correct variable, actually use variable correctly and added a loadConfig entry 2021-11-09 21:18:54 +01:00
Stefan Schokker
556c31d4ea Add checkbox that allows invalid SMTP certificates to be used. 2021-11-08 20:48:55 +01:00
Harvey Tindall
0bf8cd65cd add option to set new expiry for when re-enabling users
for this reddit comment: https://www.reddit.com/r/jellyfin/comments/nc6tsi/tip_jfago_is_awesome/hgh0yet/?context=3
2021-10-18 20:39:23 +01:00
Harvey Tindall
4d27f7fc7a user mediabrowser 0.3.6 2021-10-13 15:22:06 +01:00
Harvey Tindall
a4f59203b0 pwr: fix Send PWR when not using "Set password" 2021-10-13 15:19:52 +01:00
Harvey Tindall
eeb9b07bce admin: add manual "Send Password reset" to accounts tab
Only appears with Reset links enabled.
Pressing this sends a PWR link to the users selected.
if one user is selected, or if one of you selected users doesn't have a
method of contact, a link is given to the admin to send to them
manually.
2021-10-13 15:04:22 +01:00
Cornichon420
9ae16163bb Translated using Weblate (French)
Currently translated at 100.0% (109 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/fr/
2021-10-07 17:19:30 +02:00
Sundune
c5ce66bd4d translation from Weblate (German)
Currently translated at 98.2% (165 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/de/
2021-10-07 17:19:30 +02:00
Cornichon420
da8dd7def8 translation from Weblate (French)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/fr/
2021-10-07 17:19:29 +02:00
Cornichon420
a4b5d6dea8 translation from Weblate (French)
Currently translated at 100.0% (168 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/fr/
2021-10-07 17:19:29 +02:00
Harvey Tindall
77799b2917 smtp: only construct email once
also switch layout in email file to group each driver and its methods
together.
2021-10-07 16:19:06 +01:00
Harvey Tindall
5ff3839239 merge lang 2021-10-07 12:03:15 +01:00
Harvey Tindall
d560df5b1e switch smtp library, add, HELLO hostname option
now using xhit/go-simple-mail, as I wanted to add an option to change
the hostname sent in the HELLO message but this is only possible with
STARTTLS in jordan-wright/email. New option can be seen in Settings >
SMTP with advanced settings turned on.
2021-10-07 12:01:42 +01:00
Richard de Boer
91c8ce8089 Translated using Weblate (Dutch)
Currently translated at 100.0% (109 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/nl/
2021-10-03 21:33:49 +02:00
Harvey Tindall
0fe72b41bf lowercase lang 2021-10-03 16:45:51 +01:00
xarmadigi
a1d93cd6af Translated using Weblate (Spanish)
Currently translated at 83.3% (5 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/chat-bots/es/
2021-10-03 17:37:22 +02:00
Luis Rhenals
53ac01eda4 Translated using Weblate (Spanish)
Currently translated at 88.8% (8 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/es/
2021-10-03 17:37:22 +02:00
Luis Rhenals
4cea755065 translation from Weblate (Spanish)
Currently translated at 100.0% (168 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2021-10-03 17:37:22 +02:00
Luis Rhenals
be4e83d69c Translated using Weblate (Spanish)
Currently translated at 85.0% (17 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/es/
2021-10-03 17:37:22 +02:00
xarmadigi
1e58a33c68 Translated using Weblate (Spanish)
Currently translated at 85.0% (17 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/es/
2021-10-03 17:37:22 +02:00
xarmadigi
15e1766920 Added translation using Weblate (Spanish) 2021-10-03 17:37:22 +02:00
Harvey Tindall
a220ba8dfb fix NewEmptyLogger
for #151.
Dumb bug, I really need unit tests.
2021-10-03 15:59:58 +01:00
Harvey Tindall
b29c24a405 merge lang 2021-09-18 13:44:54 +01:00
Harvey Tindall
fbe3553b25 fix missing last log line
Sometimes calls to app.err.Fatalf would fail to print the error to the
console, and fail to show "A crash report has been saves to...". Both of
these should be fixed now.
2021-09-18 13:43:11 +01:00
xarmadigi
f727e2c5b2 translation from Weblate (Spanish)
Currently translated at 88.0% (148 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/es/
2021-09-04 23:09:56 +02:00
XCQi
6c6af623a6 translation from Weblate (Chinese (Simplified))
Currently translated at 100.0% (168 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/zh_Hans/
2021-08-28 21:55:25 +02:00
Harvey Tindall
548dceda28 don't give smtp plainauth if no username & password
for #141, just a guess
2021-08-26 21:03:02 +01:00
Harvey Tindall
e67b2e91fb invite: auto-append /invite if missing to url_base 2021-08-26 18:53:22 +01:00
Harvey Tindall
412fe31da6 invite: fix email confirmation jwt
same issue as with auth.go, expiry was a string causing the library to
see it as expired.
2021-08-26 18:39:50 +01:00
Harvey Tindall
1bfec54c93 print error and include in txt log on crash; fix email migration 2021-08-25 18:10:06 +01:00
201 changed files with 23165 additions and 8904 deletions

View File

@@ -80,12 +80,15 @@ steps:
volumes:
- name: ssh_key
path: /id_rsa
- name: ssh_key2
path: /id_rsa2
commands:
- curl -sL https://git.io/goreleaser > goreleaser
- chmod +x goreleaser
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --rm-dist
- wget https://builds.hrfee.pw/upload.py
- pip3 install requests
- bash -c 'sftp -i /id_rsa2 -o StrictHostKeyChecking=no root@161.97.102.153:/mnt/redoc <<< $"put docs/swagger.json jfa-go.json"'
- bash -c 'sftp -P 2022 -i /id_rsa -o StrictHostKeyChecking=no root@161.97.102.153:/repo/incoming <<< $"put dist/*.deb"'
# - bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "reprepro -Vb /repo remove trusty-unstable jfa-go"'
# - bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "reprepro -Vb /repo remove trusty-unstable jfa-go-tray"'
@@ -99,6 +102,9 @@ volumes:
- name: ssh_key
host:
path: /root/.ssh/id_rsa_packaging
- name: ssh_key2
host:
path: /root/.ssh/docker-build
trigger:
branch:
- main

View File

@@ -7,7 +7,7 @@ assignees: ''
---
#### Read the [FAQ](https://github.com/hrfee/jfa-go/wiki/FAQ) first!
#### Read the [FAQ](https://wiki.jfa-go.com/docs/faq/) first!
**Describe the bug**
@@ -21,7 +21,7 @@ What to do to reproduce the problem.
**If you're using a build with a tray icon, right-click on it and press "Open logs" to access your logs.**
When you notice the problem, check the output of `jfa-go`. If the problem is not obvious (e.g a panic (red text) or 'ERROR' log), re-run jfa-go with the `-debug` argument and reproduce the problem. You should then take a screenshot of the output, or paste it here, preferably between \`\`\` tags (e.g \`\`\``Log here`\`\`\`). Remember to censor any personal information.
When you notice the problem, check the output of `jfa-go` or get the logs by pressing the "Logs" button in the Settings tab. If the problem is not obvious (e.g a panic (red text) or 'ERROR' log), re-run jfa-go with the `-debug` argument and reproduce the problem. You should then take a screenshot of the output, or paste it here, preferably between \`\`\` tags (e.g \`\`\``Log here`\`\`\`). Remember to censor any personal information.
If nothing catches your eye in the log, access the admin page via your browser, go into the console (Right click > Inspect Element > Console), refresh, reproduce the problem then paste the output here in the same way as above.

2
.gitignore vendored
View File

@@ -1,6 +1,7 @@
node_modules/
site/node_modules/
site/out/
site/tempts/
mail/*.html
dist/
build/
@@ -18,4 +19,5 @@ instructions-debian.txt
cl.md
./telegram/
mautrix/
tempts/
matacc.txt

View File

@@ -13,33 +13,42 @@ before:
- npm install
- npm install esbuild
- mkdir -p data/web/css
- npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --minify
- cp node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 data/web/css/
- cp -r html data/
- node scripts/missing-colors.js html data/html
- cp -r lang data/
- cp LICENSE data/
- cp jfa-go.service data/
- python3 scripts/enumerate_config.py -i config/config-base.json -o data/config-base.json
- python3 scripts/generate_ini.py -i config/config-base.json -o data/config-default.ini
- python3 scripts/compile_mjml.py -o data/
- npx esbuild --bundle ts/admin.ts --outfile=./data/web/js/admin.js --minify
- npx esbuild --bundle ts/pwr.ts --outfile=./data/web/js/pwr.js --minify
- npx esbuild --bundle ts/form.ts --outfile=./data/web/js/form.js --minify
- npx esbuild --bundle ts/setup.ts --outfile=./data/web/js/setup.js --minify
- npx esbuild --bundle ts/crash.ts --outfile=./data/crash.js --minify
- rm -rf tempts
- cp -r ts tempts
- scripts/dark-variant.sh tempts
- scripts/dark-variant.sh tempts/modules
- npx esbuild --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify
- npx esbuild --bundle tempts/pwr.ts --outfile=./data/web/js/pwr.js --minify
- npx esbuild --bundle tempts/form.ts --outfile=./data/web/js/form.js --minify
- npx esbuild --bundle tempts/setup.ts --outfile=./data/web/js/setup.js --minify
- npx esbuild --bundle tempts/crash.ts --outfile=./data/crash.js --minify
- rm -r tempts
- npx esbuild --bundle css/base.css --outfile=./data/web/css/bundle.css --external:remixicon.css --minify
- cp html/crash.html data/
- npx uncss data/crash.html --csspath web/css --output data/bundle.css
- npx inline-source --root data data/crash.html data/crash.html
- npx tailwindcss -i data/web/css/bundle.css -o data/bundle.css --content "html/crash.html"
- node scripts/inline.js root data data/crash.html data/crash.html
- rm data/bundle.css
- npx tailwindcss -i data/web/css/bundle.css -o data/web/css/bundle.css
- mv data/crash.html data/html/
- go get -u github.com/swaggo/swag/cmd/swag
- go install github.com/swaggo/swag/cmd/swag@latest
- swag init -g main.go
- mv data/web/css/bundle.css data/web/css/{{.Env.JFA_GO_CSS_VERSION}}bundle.css
builds:
- id: notray
dir: ./
env:
- CGO_ENABLED=0
ldflags:
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}}
goos:
- linux
- darwin
@@ -56,7 +65,7 @@ builds:
flags:
- -tags=tray
ldflags:
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -H=windowsgui
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -H=windowsgui
goos:
- windows
goarch:
@@ -68,7 +77,7 @@ builds:
flags:
- -tags=tray
ldflags:
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary
- -s -w -X main.version={{.Env.JFA_GO_VERSION}} -X main.commit={{.ShortCommit}} -X main.updater=binary -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}}
goos:
- linux
goarch:

View File

@@ -4,9 +4,9 @@ COPY . /opt/build
RUN apt-get update -y \
&& apt-get install build-essential python3-pip curl software-properties-common sed -y \
&& (curl -sL https://deb.nodesource.com/setup_14.x | bash -) \
&& (curl -sL https://deb.nodesource.com/setup_current.x | bash -) \
&& apt-get install nodejs \
&& (cd /opt/build; make configuration npm email typescript bundle-css inline swagger copy INTERNAL=off GOESBUILD=on) \
&& (cd /opt/build; make configuration npm email typescript variants-html bundle-css inline-css swagger copy INTERNAL=off GOESBUILD=on) \
&& sed -i 's#id="password_resets-watch_directory" placeholder="/config/jellyfin"#id="password_resets-watch_directory" value="/jf" disabled#g' /opt/build/build/data/html/setup.html

View File

@@ -2,7 +2,7 @@
MIT License
Copyright (c) 2021 Harvey Tindall
Copyright (c) 2023 Harvey Tindall
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

View File

@@ -6,12 +6,14 @@ else
endif
GOBINARY ?= go
CSSVERSION ?= v3
VERSION ?= $(shell git describe --exact-match HEAD 2> /dev/null || echo vgit)
VERSION := $(shell echo $(VERSION) | sed 's/v//g')
COMMIT ?= $(shell git rev-parse --short HEAD || echo unknown)
UPDATER ?= off
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT)
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.cssVersion=$(CSSVERSION)
ifeq ($(UPDATER), on)
LDFLAGS := $(LDFLAGS) -X main.updater=binary
else ifneq ($(UPDATER), off)
@@ -50,16 +52,19 @@ endif
DEBUG ?= off
ifeq ($(DEBUG), on)
SOURCEMAP := --sourcemap
TYPECHECK := tsc -noEmit --project ts/tsconfig.json
TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json
# jank
COPYTS := rm -r $(DATA)/web/js/ts; cp -r ts $(DATA)/web/js
COPYTS := rm -r $(DATA)/web/js/ts; cp -r tempts $(DATA)/web/js/ts
UNCSS := cp $(DATA)/web/css/bundle.css $(DATA)/bundle.css
# TAILWIND := --content ""
else
LDFLAGS := -s -w $(LDFLAGS)
SOURCEMAP :=
COPYTS :=
TYPECHECK :=
UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
UNCSS := npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/bundle.css --content "html/crash.html"
# UNCSS := npx uncss $(DATA)/crash.html --csspath web/css --output $(DATA)/bundle.css
TAILWIND :=
endif
RACE ?= off
@@ -75,7 +80,7 @@ npm:
@if [ "$(GOESBUILD)" = "off" ]; then\
npm install esbuild;\
else\
go get -u github.com/evanw/esbuild/cmd/esbuild;\
go install github.com/evanw/esbuild/cmd/esbuild@latest;\
fi
configuration:
@@ -91,17 +96,22 @@ email:
typescript:
$(TYPECHECK)
$(adding dark variants to typescript)
rm -rf tempts
cp -r ts tempts
scripts/dark-variant.sh tempts
scripts/dark-variant.sh tempts/modules
$(info compiling typescript)
-mkdir -p $(DATA)/web/js
-$(ESBUILD) --bundle ts/admin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/admin.js --minify
-$(ESBUILD) --bundle ts/pwr.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr.js --minify
-$(ESBUILD) --bundle ts/form.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/form.js --minify
-$(ESBUILD) --bundle ts/setup.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/setup.js --minify
-$(ESBUILD) --bundle ts/crash.ts --outfile=./$(DATA)/crash.js --minify
mkdir -p $(DATA)/web/js
$(ESBUILD) --target=es6 --bundle tempts/admin.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/admin.js --minify
$(ESBUILD) --target=es6 --bundle tempts/pwr.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/pwr.js --minify
$(ESBUILD) --target=es6 --bundle tempts/form.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/form.js --minify
$(ESBUILD) --target=es6 --bundle tempts/setup.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/setup.js --minify
$(ESBUILD) --target=es6 --bundle tempts/crash.ts --outfile=./$(DATA)/crash.js --minify
$(COPYTS)
swagger:
$(GOBINARY) get github.com/swaggo/swag/cmd/swag
$(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest
swag init -g main.go
compile:
@@ -115,30 +125,38 @@ compress:
upx --lzma build/jfa-go
bundle-css:
-mkdir -p $(DATA)/web/css
$(info bundling css)
$(ESBUILD) --bundle css/base.css --outfile=$(DATA)/web/css/bundle.css --external:remixicon.css --minify
inline:
cp html/crash.html $(DATA)/crash.html
$(UNCSS)
npx inline-source --root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
rm $(DATA)/bundle.css
copy:
mkdir -p $(DATA)/web/css
$(info copying fonts)
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 $(DATA)/web/css/
$(info bundling css)
$(ESBUILD) --bundle css/base.css --outfile=$(DATA)/web/css/bundle.css --external:remixicon.css --minify
npx tailwindcss -i $(DATA)/web/css/bundle.css -o $(DATA)/web/css/bundle.css $(TAILWIND)
# npx postcss -o $(DATA)/web/css/bundle.css $(DATA)/web/css/bundle.css
inline-css:
cp html/crash.html $(DATA)/crash.html
$(UNCSS)
node scripts/inline.js root $(DATA) $(DATA)/crash.html $(DATA)/crash.html
rm $(DATA)/bundle.css
variants-html:
$(info copying html)
cp -r html $(DATA)/
$(info adding dark variants to html)
node scripts/missing-colors.js html $(DATA)/html
copy:
$(info copying crash page)
mv $(DATA)/crash.html $(DATA)/html/
$(info copying static data)
-mkdir -p $(DATA)/web
mkdir -p $(DATA)/web
cp -r static/* $(DATA)/web/
$(info copying systemd service)
cp jfa-go.service $(DATA)/
$(info copying language files)
cp -r lang $(DATA)/
cp LICENSE $(DATA)/
mv $(DATA)/web/css/bundle.css $(DATA)/web/css/$(CSSVERSION)bundle.css
# internal-files:
# python3 scripts/embed.py internal
@@ -159,4 +177,4 @@ clean:
-rm docs/docs.go docs/swagger.json docs/swagger.yaml
go clean
all: configuration npm email typescript bundle-css inline swagger copy compile
all: configuration npm email typescript variants-html bundle-css inline-css swagger copy compile

View File

@@ -3,9 +3,25 @@
[![Docker Hub](https://img.shields.io/docker/pulls/hrfee/jfa-go?label=docker)](https://hub.docker.com/r/hrfee/jfa-go)
[![Translation status](https://weblate.jfa-go.com/widgets/jfa-go/-/svg-badge.svg)](https://weblate.jfa-go.com/engage/jfa-go/)
[![Docs/Wiki](https://img.shields.io/static/v1?label=documentation&message=jfa-go.com&color=informational)](https://wiki.jfa-go.com)
[![Discord](https://img.shields.io/discord/922842034170122321?color=%235865F2&label=discord)](https://discord.com/invite/MrtvuQmyhP)
##### Downloads:
##### [docker](#docker) | [debian/ubuntu](#debian) | [arch (aur)](#aur) | [other platforms](#other-platforms)
---
## Project Status
Due to studies and general lack of enthusiasm for work on this project, new features are unlikely, and while occasionally I might fix a bug or two, I won't be supporting the project a lot.
#### Does it still work?
jfa-go still appears to work on the latest version of Jellyfin (10.8.9), and unless any large architectural changes occur to it, functionality should still remain.
#### Alternatives
None of these have been tested by myself, but I have seen them mentioned quite frequently.
* [Wizarr](https://github.com/Wizarrrr/wizarr) focuses on invites, and also includes some Discord & Ombi integration.
* [Jellyseerr](https://github.com/Fallenbagel/jellyseerr) is a fork of Overseerr, which can manage users and mainly acts as an Ombi alternative.
* [Organizr](https://github.com/causefx/Organizr) doesn't focus on Jellyfin, but allows putting self-hosted services into "tabs" on a central page, and allows creating users, which lets one control who can access what.
---
jfa-go is a user management app for [Jellyfin](https://github.com/jellyfin/jellyfin) (and now [Emby](https://emby.media/)) that provides invite-based account creation as well as other features that make one's instance much easier to manage.
@@ -18,14 +34,15 @@ a rewrite of [jellyfin-accounts](https://github.com/hrfee/jellyfin-accounts) (or
* Granular control over invites: Validity period as well as number of uses can be specified.
* Account profiles: Assign settings profiles to invites so new users have your predefined permissions, homescreen layout, etc. applied to their account on creation.
* Password validation: Ensure users choose a strong password.
* CAPTCHAs can be enabled to avoid bots
* ⌛ User expiry: Specify a validity period, and new users accounts will be disabled/deleted after it. The period can be manually extended too.
* 🔗 Ombi Integration: Automatically creates Ombi accounts for new users using their email address and login details, and your own defined set of permissions.
* Account management: Apply settings to your users individually or en masse, and delete users, optionally sending them an email notification with a reason.
* Telegram/Discord/Matrix Integration: Verify users via a chat bot, and send Password Resets, Announcements, etc. through it.
* 📨 Email storage: Add your existing users email addresses through the UI, and jfa-go will ask new users for them on account creation.
* Email addresses can optionally be used instead of usernames
* 🔑 Password resets: When users forget their passwords and request a change in Jellyfin, jfa-go reads the PIN from the created file and sends it straight to the user via email/telegram.
* Notifications: Get notified when someone creates an account, or an invite expires.
* 🔑 Password resets: When users forget their passwords and request a change in Jellyfin, jfa-go reads the PIN from the created file and sends it straight to them via email/telegram.
* Admin Notifications: Get notified when someone creates an account, or an invite expires.
* 📣 Announcements: Bulk message your users with announcements about your server.
* Authentication via Jellyfin: Instead of using separate credentials for jfa-go and Jellyfin, jfa-go can use it as the authentication provider.
* Enables the usage of jfa-go by multiple people
@@ -149,8 +166,13 @@ If you're switching from jellyfin-accounts, copy your existing `~/.jf-accounts`
(or specify config/data path with `-config/-data` respectively.)
#### Contributing
See [CONTRIBUTING.md](https://github.com/hrfee/jfa-go/blob/main/CONTRIBUTING.md).
See [the wiki page](https://wiki.jfa-go.com/docs/dev/) or [CONTRIBUTING.md](https://github.com/hrfee/jfa-go/blob/main/CONTRIBUTING.md).
##### Translation
[![Translation status](https://weblate.jfa-go.com/widgets/jfa-go/-/multi-auto.svg)](https://weblate.jfa-go.com/engage/jfa-go/)
For translations, use the weblate instance [here](https://weblate.jfa-go.com/engage/jfa-go/). You can login with github.
#### Sponsors
Big thanks to those who sponsor me. You can see them below:
[<img src="https://sponsors-endpoint.hrfee.pw/sponsor/avatar/0" width="35">](https://sponsors-endpoint.hrfee.pw/sponsor/profile/0)

432
api-invites.go Normal file
View File

@@ -0,0 +1,432 @@
package main
import (
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3"
)
func (app *appContext) checkInvites() {
currentTime := time.Now()
app.storage.loadInvites()
changed := false
for code, data := range app.storage.invites {
expiry := data.ValidTill
if !currentTime.After(expiry) {
continue
}
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code)
var wait sync.WaitGroup
for address, settings := range notify {
if !settings["notify-expiry"] {
continue
}
wait.Add(1)
go func(addr string) {
defer wait.Done()
msg, err := app.email.constructExpiry(code, data, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
} else {
// Check whether notify "address" is an email address of Jellyfin ID
if strings.Contains(addr, "@") {
err = app.email.send(msg, addr)
} else {
err = app.sendByID(msg, addr)
}
if err != nil {
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
} else {
app.info.Printf("Sent expiry notification to %s", addr)
}
}
}(address)
}
wait.Wait()
}
changed = true
delete(app.storage.invites, code)
}
if changed {
app.storage.storeInvites()
}
}
func (app *appContext) checkInvite(code string, used bool, username string) bool {
currentTime := time.Now()
app.storage.loadInvites()
changed := false
inv, match := app.storage.invites[code]
if !match {
return false
}
expiry := inv.ValidTill
if currentTime.After(expiry) {
app.debug.Printf("Housekeeping: Deleting old invite %s", code)
notify := inv.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", code)
var wait sync.WaitGroup
for address, settings := range notify {
if !settings["notify-expiry"] {
continue
}
wait.Add(1)
go func(addr string) {
defer wait.Done()
msg, err := app.email.constructExpiry(code, inv, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", code, err)
} else {
// Check whether notify "address" is an email address of Jellyfin ID
if strings.Contains(addr, "@") {
err = app.email.send(msg, addr)
} else {
err = app.sendByID(msg, addr)
}
if err != nil {
app.err.Printf("%s: Failed to send expiry notification: %v", code, err)
} else {
app.info.Printf("Sent expiry notification to %s", addr)
}
}
}(address)
}
wait.Wait()
}
changed = true
match = false
delete(app.storage.invites, code)
} else if used {
changed = true
del := false
newInv := inv
if newInv.RemainingUses == 1 {
del = true
delete(app.storage.invites, code)
} else if newInv.RemainingUses != 0 {
// 0 means infinite i guess?
newInv.RemainingUses--
}
newInv.UsedBy = append(newInv.UsedBy, []string{username, strconv.FormatInt(currentTime.Unix(), 10)})
if !del {
app.storage.invites[code] = newInv
}
}
if changed {
app.storage.storeInvites()
}
return match
}
// @Summary Create a new invite.
// @Produce json
// @Param generateInviteDTO body generateInviteDTO true "New invite request object"
// @Success 200 {object} boolResponse
// @Router /invites [post]
// @Security Bearer
// @tags Invites
func (app *appContext) GenerateInvite(gc *gin.Context) {
var req generateInviteDTO
app.debug.Println("Generating new invite")
app.storage.loadInvites()
gc.BindJSON(&req)
currentTime := time.Now()
validTill := currentTime.AddDate(0, req.Months, req.Days)
validTill = validTill.Add(time.Hour*time.Duration(req.Hours) + time.Minute*time.Duration(req.Minutes))
// make sure code doesn't begin with number
inviteCode := shortuuid.New()
_, err := strconv.Atoi(string(inviteCode[0]))
for err == nil {
inviteCode = shortuuid.New()
_, err = strconv.Atoi(string(inviteCode[0]))
}
var invite Invite
if req.Label != "" {
invite.Label = req.Label
}
invite.Created = currentTime
if req.MultipleUses {
if req.NoLimit {
invite.NoLimit = true
} else {
invite.RemainingUses = req.RemainingUses
}
} else {
invite.RemainingUses = 1
}
invite.UserExpiry = req.UserExpiry
if invite.UserExpiry {
invite.UserMonths = req.UserMonths
invite.UserDays = req.UserDays
invite.UserHours = req.UserHours
invite.UserMinutes = req.UserMinutes
}
invite.ValidTill = validTill
if req.SendTo != "" && app.config.Section("invite_emails").Key("enabled").MustBool(false) {
addressValid := false
discord := ""
app.debug.Printf("%s: Sending invite message", inviteCode)
if discordEnabled && !strings.Contains(req.SendTo, "@") {
users := app.discord.GetUsers(req.SendTo)
if len(users) == 0 {
invite.SendTo = fmt.Sprintf("Failed: User not found: \"%s\"", req.SendTo)
} else if len(users) > 1 {
invite.SendTo = fmt.Sprintf("Failed: Multiple users found: \"%s\"", req.SendTo)
} else {
invite.SendTo = req.SendTo
addressValid = true
discord = users[0].User.ID
}
} else if emailEnabled {
addressValid = true
invite.SendTo = req.SendTo
}
if addressValid {
msg, err := app.email.constructInvite(inviteCode, invite, app, false)
if err != nil {
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
app.err.Printf("%s: Failed to construct invite message: %v", inviteCode, err)
} else {
var err error
if discord != "" {
err = app.discord.SendDM(msg, discord)
} else {
err = app.email.send(msg, req.SendTo)
}
if err != nil {
invite.SendTo = fmt.Sprintf("Failed to send to %s", req.SendTo)
app.err.Printf("%s: %s: %v", inviteCode, invite.SendTo, err)
} else {
app.info.Printf("%s: Sent invite email to \"%s\"", inviteCode, req.SendTo)
}
}
}
}
if req.Profile != "" {
if _, ok := app.storage.profiles[req.Profile]; ok {
invite.Profile = req.Profile
} else {
invite.Profile = "Default"
}
}
app.storage.invites[inviteCode] = invite
app.storage.storeInvites()
respondBool(200, true, gc)
}
// @Summary Get invites.
// @Produce json
// @Success 200 {object} getInvitesDTO
// @Router /invites [get]
// @Security Bearer
// @tags Invites
func (app *appContext) GetInvites(gc *gin.Context) {
app.debug.Println("Invites requested")
currentTime := time.Now()
app.storage.loadInvites()
app.checkInvites()
var invites []inviteDTO
for code, inv := range app.storage.invites {
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
invite := inviteDTO{
Code: code,
Months: months,
Days: days,
Hours: hours,
Minutes: minutes,
UserExpiry: inv.UserExpiry,
UserMonths: inv.UserMonths,
UserDays: inv.UserDays,
UserHours: inv.UserHours,
UserMinutes: inv.UserMinutes,
Created: inv.Created.Unix(),
Profile: inv.Profile,
NoLimit: inv.NoLimit,
Label: inv.Label,
}
if len(inv.UsedBy) != 0 {
invite.UsedBy = map[string]int64{}
for _, pair := range inv.UsedBy {
// These used to be stored formatted instead of as a unix timestamp.
unix, err := strconv.ParseInt(pair[1], 10, 64)
if err != nil {
date, err := timefmt.Parse(pair[1], app.datePattern+" "+app.timePattern)
if err != nil {
app.err.Printf("Failed to parse usedBy time: %v", err)
}
unix = date.Unix()
}
invite.UsedBy[pair[0]] = unix
}
}
invite.RemainingUses = 1
if inv.RemainingUses != 0 {
invite.RemainingUses = inv.RemainingUses
}
if inv.SendTo != "" {
invite.SendTo = inv.SendTo
}
if len(inv.Notify) != 0 {
var address string
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
app.storage.loadEmails()
if addr, ok := app.storage.emails[gc.GetString("jfId")]; ok && addr.Addr != "" {
address = addr.Addr
}
} else {
address = app.config.Section("ui").Key("email").String()
}
if _, ok := inv.Notify[address]; ok {
if _, ok = inv.Notify[address]["notify-expiry"]; ok {
invite.NotifyExpiry = inv.Notify[address]["notify-expiry"]
}
if _, ok = inv.Notify[address]["notify-creation"]; ok {
invite.NotifyCreation = inv.Notify[address]["notify-creation"]
}
}
}
invites = append(invites, invite)
}
profiles := make([]string, len(app.storage.profiles))
if len(app.storage.profiles) != 0 {
profiles[0] = app.storage.defaultProfile
i := 1
if len(app.storage.profiles) > 1 {
for p := range app.storage.profiles {
if p != app.storage.defaultProfile {
profiles[i] = p
i++
}
}
}
}
resp := getInvitesDTO{
Profiles: profiles,
Invites: invites,
}
gc.JSON(200, resp)
}
// @Summary Set profile for an invite
// @Produce json
// @Param inviteProfileDTO body inviteProfileDTO true "Invite profile object"
// @Success 200 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /invites/profile [post]
// @Security Bearer
// @tags Profiles & Settings
func (app *appContext) SetProfile(gc *gin.Context) {
var req inviteProfileDTO
gc.BindJSON(&req)
app.debug.Printf("%s: Setting profile to \"%s\"", req.Invite, req.Profile)
// "" means "Don't apply profile"
if _, ok := app.storage.profiles[req.Profile]; !ok && req.Profile != "" {
app.err.Printf("%s: Profile \"%s\" not found", req.Invite, req.Profile)
respond(500, "Profile not found", gc)
return
}
inv := app.storage.invites[req.Invite]
inv.Profile = req.Profile
app.storage.invites[req.Invite] = inv
app.storage.storeInvites()
respondBool(200, true, gc)
}
// @Summary Set notification preferences for an invite.
// @Produce json
// @Param setNotifyDTO body setNotifyDTO true "Map of invite codes to notification settings objects"
// @Success 200
// @Failure 400 {object} stringResponse
// @Failure 500 {object} stringResponse
// @Router /invites/notify [post]
// @Security Bearer
// @tags Other
func (app *appContext) SetNotify(gc *gin.Context) {
var req map[string]map[string]bool
gc.BindJSON(&req)
changed := false
for code, settings := range req {
app.debug.Printf("%s: Notification settings change requested", code)
app.storage.loadInvites()
app.storage.loadEmails()
invite, ok := app.storage.invites[code]
if !ok {
app.err.Printf("%s Notification setting change failed: Invalid code", code)
respond(400, "Invalid invite code", gc)
return
}
var address string
jellyfinLogin := app.config.Section("ui").Key("jellyfin_login").MustBool(false)
if jellyfinLogin {
var addressAvailable bool = app.getAddressOrName(gc.GetString("jfId")) != ""
if !addressAvailable {
app.err.Printf("%s: Couldn't find contact method for admin. Make sure one is set.", code)
app.debug.Printf("%s: User ID \"%s\"", code, gc.GetString("jfId"))
respond(500, "Missing user contact method", gc)
return
}
address = gc.GetString("jfId")
} else {
address = app.config.Section("ui").Key("email").String()
}
if invite.Notify == nil {
invite.Notify = map[string]map[string]bool{}
}
if _, ok := invite.Notify[address]; !ok {
invite.Notify[address] = map[string]bool{}
} /*else {
if _, ok := invite.Notify[address]["notify-expiry"]; !ok {
*/
if _, ok := settings["notify-expiry"]; ok && invite.Notify[address]["notify-expiry"] != settings["notify-expiry"] {
invite.Notify[address]["notify-expiry"] = settings["notify-expiry"]
app.debug.Printf("%s: Set \"notify-expiry\" to %t for %s", code, settings["notify-expiry"], address)
changed = true
}
if _, ok := settings["notify-creation"]; ok && invite.Notify[address]["notify-creation"] != settings["notify-creation"] {
invite.Notify[address]["notify-creation"] = settings["notify-creation"]
app.debug.Printf("%s: Set \"notify-creation\" to %t for %s", code, settings["notify-creation"], address)
changed = true
}
if changed {
app.storage.invites[code] = invite
}
}
if changed {
app.storage.storeInvites()
}
}
// @Summary Delete an invite.
// @Produce json
// @Param deleteInviteDTO body deleteInviteDTO true "Delete invite object"
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Router /invites [delete]
// @Security Bearer
// @tags Invites
func (app *appContext) DeleteInvite(gc *gin.Context) {
var req deleteInviteDTO
gc.BindJSON(&req)
app.debug.Printf("%s: Deletion requested", req.Code)
var ok bool
_, ok = app.storage.invites[req.Code]
if ok {
delete(app.storage.invites, req.Code)
app.storage.storeInvites()
app.info.Printf("%s: Invite deleted", req.Code)
respondBool(200, true, gc)
return
}
app.err.Printf("%s: Deletion failed: Invalid code", req.Code)
respond(400, "Code doesn't exist", gc)
}

783
api-messages.go Normal file
View File

@@ -0,0 +1,783 @@
package main
import (
"strings"
"time"
"github.com/gin-gonic/gin"
"gopkg.in/ini.v1"
)
// @Summary Get a list of email names and IDs.
// @Produce json
// @Param lang query string false "Language for email titles."
// @Success 200 {object} emailListDTO
// @Router /config/emails [get]
// @Security Bearer
// @tags Configuration
func (app *appContext) GetCustomEmails(gc *gin.Context) {
lang := gc.Query("lang")
if _, ok := app.storage.lang.Email[lang]; !ok {
lang = app.storage.lang.chosenEmailLang
}
gc.JSON(200, emailListDTO{
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.customEmails.UserCreated.Enabled},
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.customEmails.InviteExpiry.Enabled},
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.customEmails.PasswordReset.Enabled},
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.customEmails.UserDeleted.Enabled},
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.customEmails.UserDisabled.Enabled},
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.customEmails.UserEnabled.Enabled},
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.customEmails.InviteEmail.Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.customEmails.WelcomeEmail.Enabled},
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.customEmails.EmailConfirmation.Enabled},
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.customEmails.UserExpired.Enabled},
})
}
func (app *appContext) getCustomEmail(id string) *customEmail {
switch id {
case "Announcement":
return &customEmail{}
case "UserCreated":
return &app.storage.customEmails.UserCreated
case "InviteExpiry":
return &app.storage.customEmails.InviteExpiry
case "PasswordReset":
return &app.storage.customEmails.PasswordReset
case "UserDeleted":
return &app.storage.customEmails.UserDeleted
case "UserDisabled":
return &app.storage.customEmails.UserDisabled
case "UserEnabled":
return &app.storage.customEmails.UserEnabled
case "InviteEmail":
return &app.storage.customEmails.InviteEmail
case "WelcomeEmail":
return &app.storage.customEmails.WelcomeEmail
case "EmailConfirmation":
return &app.storage.customEmails.EmailConfirmation
case "UserExpired":
return &app.storage.customEmails.UserExpired
}
return nil
}
// @Summary Sets the corresponding custom email.
// @Produce json
// @Param customEmail body customEmail true "Content = email (in markdown)."
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param id path string true "ID of email"
// @Router /config/emails/{id} [post]
// @Security Bearer
// @tags Configuration
func (app *appContext) SetCustomEmail(gc *gin.Context) {
var req customEmail
gc.BindJSON(&req)
id := gc.Param("id")
if req.Content == "" {
respondBool(400, false, gc)
return
}
email := app.getCustomEmail(id)
if email == nil {
respondBool(400, false, gc)
return
}
email.Content = req.Content
email.Enabled = true
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Enable/Disable custom email.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param enable/disable path string true "enable/disable"
// @Param id path string true "ID of email"
// @Router /config/emails/{id}/state/{enable/disable} [post]
// @Security Bearer
// @tags Configuration
func (app *appContext) SetCustomEmailState(gc *gin.Context) {
id := gc.Param("id")
s := gc.Param("state")
enabled := false
if s == "enable" {
enabled = true
} else if s != "disable" {
respondBool(400, false, gc)
}
email := app.getCustomEmail(id)
if email == nil {
respondBool(400, false, gc)
return
}
email.Enabled = enabled
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Returns the custom email (generating it if not set) and list of used variables in it.
// @Produce json
// @Success 200 {object} customEmailDTO
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param id path string true "ID of email"
// @Router /config/emails/{id} [get]
// @Security Bearer
// @tags Configuration
func (app *appContext) GetCustomEmailTemplate(gc *gin.Context) {
lang := app.storage.lang.chosenEmailLang
id := gc.Param("id")
var content string
var err error
var msg *Message
var variables []string
var conditionals []string
var values map[string]interface{}
username := app.storage.lang.Email[lang].Strings.get("username")
emailAddress := app.storage.lang.Email[lang].Strings.get("emailAddress")
email := app.getCustomEmail(id)
if email == nil {
app.err.Printf("Failed to get custom email with ID \"%s\"", id)
respondBool(400, false, gc)
return
}
if id == "WelcomeEmail" {
conditionals = []string{"{yourAccountWillExpire}"}
email.Conditionals = conditionals
}
content = email.Content
noContent := content == ""
if !noContent {
variables = email.Variables
}
switch id {
case "Announcement":
// Just send the email html
content = ""
case "UserCreated":
if noContent {
msg, err = app.email.constructCreated("", "", "", Invite{}, app, true)
}
values = app.email.createdValues("xxxxxx", username, emailAddress, Invite{}, app, false)
case "InviteExpiry":
if noContent {
msg, err = app.email.constructExpiry("", Invite{}, app, true)
}
values = app.email.expiryValues("xxxxxx", Invite{}, app, false)
case "PasswordReset":
if noContent {
msg, err = app.email.constructReset(PasswordReset{}, app, true)
}
values = app.email.resetValues(PasswordReset{Pin: "12-34-56", Username: username}, app, false)
case "UserDeleted":
if noContent {
msg, err = app.email.constructDeleted("", app, true)
}
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
case "UserDisabled":
if noContent {
msg, err = app.email.constructDisabled("", app, true)
}
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
case "UserEnabled":
if noContent {
msg, err = app.email.constructEnabled("", app, true)
}
values = app.email.deletedValues(app.storage.lang.Email[lang].Strings.get("reason"), app, false)
case "InviteEmail":
if noContent {
msg, err = app.email.constructInvite("", Invite{}, app, true)
}
values = app.email.inviteValues("xxxxxx", Invite{}, app, false)
case "WelcomeEmail":
if noContent {
msg, err = app.email.constructWelcome("", time.Time{}, app, true)
}
values = app.email.welcomeValues(username, time.Now(), app, false, true)
case "EmailConfirmation":
if noContent {
msg, err = app.email.constructConfirmation("", "", "", app, true)
}
values = app.email.confirmationValues("xxxxxx", username, "xxxxxx", app, false)
case "UserExpired":
if noContent {
msg, err = app.email.constructUserExpired(app, true)
}
values = app.email.userExpiredValues(app, false)
}
if err != nil {
respondBool(500, false, gc)
return
}
if noContent && id != "Announcement" {
content = msg.Text
variables = make([]string, strings.Count(content, "{"))
i := 0
found := false
buf := ""
for _, c := range content {
if !found && c != '{' && c != '}' {
continue
}
found = true
buf += string(c)
if c == '}' {
found = false
variables[i] = buf
buf = ""
i++
}
}
email.Variables = variables
}
if variables == nil {
variables = []string{}
}
if app.storage.storeCustomEmails() != nil {
respondBool(500, false, gc)
return
}
mail, err := app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
if err != nil {
respondBool(500, false, gc)
return
}
gc.JSON(200, customEmailDTO{Content: content, Variables: variables, Conditionals: conditionals, Values: values, HTML: mail.HTML, Plaintext: mail.Text})
}
// @Summary Returns a new Telegram verification PIN, and the bot username.
// @Produce json
// @Success 200 {object} telegramPinDTO
// @Router /telegram/pin [get]
// @Security Bearer
// @tags Other
func (app *appContext) TelegramGetPin(gc *gin.Context) {
gc.JSON(200, telegramPinDTO{
Token: app.telegram.NewAuthToken(),
Username: app.telegram.username,
})
}
// @Summary Link a Jellyfin & Telegram user together via a verification PIN.
// @Produce json
// @Param telegramSetDTO body telegramSetDTO true "Token and user's Jellyfin ID."
// @Success 200 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Router /users/telegram [post]
// @Security Bearer
// @tags Other
func (app *appContext) TelegramAddUser(gc *gin.Context) {
var req telegramSetDTO
gc.BindJSON(&req)
if req.Token == "" || req.ID == "" {
respondBool(400, false, gc)
return
}
tokenIndex := -1
for i, v := range app.telegram.verifiedTokens {
if v.Token == req.Token {
tokenIndex = i
break
}
}
if tokenIndex == -1 {
respondBool(500, false, gc)
return
}
tgToken := app.telegram.verifiedTokens[tokenIndex]
tgUser := TelegramUser{
ChatID: tgToken.ChatID,
Username: tgToken.Username,
Contact: true,
}
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
tgUser.Lang = lang
}
if app.storage.telegram == nil {
app.storage.telegram = map[string]TelegramUser{}
}
app.storage.telegram[req.ID] = tgUser
err := app.storage.storeTelegramUsers()
if err != nil {
app.err.Printf("Failed to store Telegram users: %v", err)
} else {
app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1], app.telegram.verifiedTokens[tokenIndex] = app.telegram.verifiedTokens[tokenIndex], app.telegram.verifiedTokens[len(app.telegram.verifiedTokens)-1]
app.telegram.verifiedTokens = app.telegram.verifiedTokens[:len(app.telegram.verifiedTokens)-1]
}
linkExistingOmbiDiscordTelegram(app)
respondBool(200, true, gc)
}
// @Summary Sets whether to notify a user through telegram/discord/matrix/email or not.
// @Produce json
// @Param SetContactMethodsDTO body SetContactMethodsDTO 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/contact [post]
// @Security Bearer
// @tags Other
func (app *appContext) SetContactMethods(gc *gin.Context) {
var req SetContactMethodsDTO
gc.BindJSON(&req)
if req.ID == "" {
respondBool(400, false, gc)
return
}
if tgUser, ok := app.storage.telegram[req.ID]; ok {
change := tgUser.Contact != req.Telegram
tgUser.Contact = req.Telegram
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
}
if change {
msg := ""
if !req.Telegram {
msg = " not"
}
app.debug.Printf("Telegram: User \"%s\" will%s be notified through Telegram.", tgUser.Username, msg)
}
}
if dcUser, ok := app.storage.discord[req.ID]; ok {
change := dcUser.Contact != req.Discord
dcUser.Contact = req.Discord
app.storage.discord[req.ID] = dcUser
if err := app.storage.storeDiscordUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Discord: Failed to store users: %v", err)
return
}
if change {
msg := ""
if !req.Discord {
msg = " not"
}
app.debug.Printf("Discord: User \"%s\" will%s be notified through Discord.", dcUser.Username, msg)
}
}
if mxUser, ok := app.storage.matrix[req.ID]; ok {
change := mxUser.Contact != req.Matrix
mxUser.Contact = req.Matrix
app.storage.matrix[req.ID] = mxUser
if err := app.storage.storeMatrixUsers(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Matrix: Failed to store users: %v", err)
return
}
if change {
msg := ""
if !req.Matrix {
msg = " not"
}
app.debug.Printf("Matrix: User \"%s\" will%s be notified through Matrix.", mxUser.UserID, msg)
}
}
if email, ok := app.storage.emails[req.ID]; ok {
change := email.Contact != req.Email
email.Contact = req.Email
app.storage.emails[req.ID] = email
if err := app.storage.storeEmails(); err != nil {
respondBool(500, false, gc)
app.err.Printf("Failed to store emails: %v", err)
return
}
if change {
msg := ""
if !req.Email {
msg = " not"
}
app.debug.Printf("\"%s\" will%s be notified via Email.", email.Addr, msg)
}
}
respondBool(200, true, gc)
}
// @Summary Returns true/false on whether or not a telegram PIN was verified. Requires bearer auth.
// @Produce json
// @Success 200 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Router /telegram/verified/{pin} [get]
// @Security Bearer
// @tags Other
func (app *appContext) TelegramVerified(gc *gin.Context) {
pin := gc.Param("pin")
tokenIndex := -1
for i, v := range app.telegram.verifiedTokens {
if v.Token == 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 Returns true/false on whether or not a telegram PIN was verified. Requires invite code.
// @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) TelegramVerifiedInvite(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.Token == pin {
tokenIndex = i
break
}
}
if app.config.Section("telegram").Key("require_unique").MustBool(false) {
for _, u := range app.storage.telegram {
if app.telegram.verifiedTokens[tokenIndex].Username == u.Username {
respondBool(400, false, gc)
return
}
}
}
// 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 Returns true/false on whether or not a discord PIN was verified. Requires invite code.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Param invCode path string true "invite Code"
// @Router /invite/{invCode}/discord/verified/{pin} [get]
// @tags Other
func (app *appContext) DiscordVerifiedInvite(gc *gin.Context) {
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc)
return
}
pin := gc.Param("pin")
_, ok := app.discord.verifiedTokens[pin]
if app.config.Section("discord").Key("require_unique").MustBool(false) {
for _, u := range app.storage.discord {
if app.discord.verifiedTokens[pin].ID == u.ID {
delete(app.discord.verifiedTokens, pin)
respondBool(400, false, gc)
return
}
}
}
respondBool(200, ok, gc)
}
// @Summary Returns a 10-minute, one-use Discord server invite
// @Produce json
// @Success 200 {object} DiscordInviteDTO
// @Failure 400 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param invCode path string true "invite Code"
// @Router /invite/{invCode}/discord/invite [get]
// @tags Other
func (app *appContext) DiscordServerInvite(gc *gin.Context) {
if app.discord.inviteChannelName == "" {
respondBool(400, false, gc)
return
}
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc)
return
}
invURL, iconURL := app.discord.NewTempInvite(10*60, 1)
if invURL == "" {
respondBool(500, false, gc)
return
}
gc.JSON(200, DiscordInviteDTO{invURL, iconURL})
}
// @Summary Generate and send a new PIN to a specified Matrix user.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param invCode path string true "invite Code"
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
// @Router /invite/{invCode}/matrix/user [post]
// @tags Other
func (app *appContext) MatrixSendPIN(gc *gin.Context) {
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
respondBool(401, false, gc)
return
}
var req MatrixSendPINDTO
gc.BindJSON(&req)
if req.UserID == "" {
respond(400, "errorNoUserID", gc)
return
}
if app.config.Section("matrix").Key("require_unique").MustBool(false) {
for _, u := range app.storage.matrix {
if req.UserID == u.UserID {
respondBool(400, false, gc)
return
}
}
}
ok := app.matrix.SendStart(req.UserID)
if !ok {
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Check whether a matrix PIN is valid, and mark the token as verified if so. Requires invite code.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Param invCode path string true "invite Code"
// @Param userID path string true "Matrix User ID"
// @Router /invite/{invCode}/matrix/verified/{userID}/{pin} [get]
// @tags Other
func (app *appContext) MatrixCheckPIN(gc *gin.Context) {
code := gc.Param("invCode")
if _, ok := app.storage.invites[code]; !ok {
app.debug.Println("Matrix: Invite code was invalid")
respondBool(401, false, gc)
return
}
userID := gc.Param("userID")
pin := gc.Param("pin")
user, ok := app.matrix.tokens[pin]
if !ok {
app.debug.Println("Matrix: PIN not found")
respondBool(200, false, gc)
return
}
if user.User.UserID != userID {
app.debug.Println("Matrix: User ID of PIN didn't match")
respondBool(200, false, gc)
return
}
user.Verified = true
app.matrix.tokens[pin] = user
respondBool(200, true, gc)
}
// @Summary Generates a Matrix access token from a username and password.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param MatrixLoginDTO body MatrixLoginDTO true "Username & password."
// @Router /matrix/login [post]
// @tags Other
func (app *appContext) MatrixLogin(gc *gin.Context) {
var req MatrixLoginDTO
gc.BindJSON(&req)
if req.Username == "" || req.Password == "" {
respond(400, "errorLoginBlank", gc)
return
}
token, err := app.matrix.generateAccessToken(req.Homeserver, req.Username, req.Password)
if err != nil {
app.err.Printf("Matrix: Failed to generate token: %v", err)
respond(401, "Unauthorized", gc)
return
}
tempConfig, _ := ini.Load(app.configPath)
matrix := tempConfig.Section("matrix")
matrix.Key("enabled").SetValue("true")
matrix.Key("homeserver").SetValue(req.Homeserver)
matrix.Key("token").SetValue(token)
matrix.Key("user_id").SetValue(req.Username)
if err := tempConfig.SaveTo(app.configPath); err != nil {
app.err.Printf("Failed to save config to \"%s\": %v", app.configPath, err)
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Links a Matrix user to a Jellyfin account via user IDs. Notifications are turned on by default.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param MatrixConnectUserDTO body MatrixConnectUserDTO true "User's Jellyfin ID & Matrix user ID."
// @Router /users/matrix [post]
// @tags Other
func (app *appContext) MatrixConnect(gc *gin.Context) {
var req MatrixConnectUserDTO
gc.BindJSON(&req)
if app.storage.matrix == nil {
app.storage.matrix = map[string]MatrixUser{}
}
roomID, encrypted, err := app.matrix.CreateRoom(req.UserID)
if err != nil {
app.err.Printf("Matrix: Failed to create room: %v", err)
respondBool(500, false, gc)
return
}
app.storage.matrix[req.JellyfinID] = MatrixUser{
UserID: req.UserID,
RoomID: string(roomID),
Lang: "en-us",
Contact: true,
Encrypted: encrypted,
}
app.matrix.isEncrypted[roomID] = encrypted
if err := app.storage.storeMatrixUsers(); err != nil {
app.err.Printf("Failed to store Matrix users: %v", err)
respondBool(500, false, gc)
return
}
respondBool(200, true, gc)
}
// @Summary Returns a list of matching users from a Discord guild, given a username (discriminator optional).
// @Produce json
// @Success 200 {object} DiscordUsersDTO
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param username path string true "username to search."
// @Router /users/discord/{username} [get]
// @tags Other
func (app *appContext) DiscordGetUsers(gc *gin.Context) {
name := gc.Param("username")
if name == "" {
respondBool(400, false, gc)
return
}
users := app.discord.GetUsers(name)
resp := DiscordUsersDTO{Users: make([]DiscordUserDTO, len(users))}
for i, u := range users {
resp.Users[i] = DiscordUserDTO{
Name: RenderDiscordUsername(u.User),
ID: u.User.ID,
AvatarURL: u.User.AvatarURL("32"),
}
}
gc.JSON(200, resp)
}
// @Summary Links a Discord account to a Jellyfin account via user IDs. Notifications are turned on by default.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param DiscordConnectUserDTO body DiscordConnectUserDTO true "User's Jellyfin ID & Discord ID."
// @Router /users/discord [post]
// @tags Other
func (app *appContext) DiscordConnect(gc *gin.Context) {
var req DiscordConnectUserDTO
gc.BindJSON(&req)
if req.JellyfinID == "" || req.DiscordID == "" {
respondBool(400, false, gc)
return
}
user, ok := app.discord.NewUser(req.DiscordID)
if !ok {
respondBool(500, false, gc)
return
}
app.storage.discord[req.JellyfinID] = user
if err := app.storage.storeDiscordUsers(); err != nil {
app.err.Printf("Failed to store Discord users: %v", err)
respondBool(500, false, gc)
return
}
linkExistingOmbiDiscordTelegram(app)
respondBool(200, true, gc)
}
// @Summary unlink a Discord account from a Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
// @Router /users/discord [delete]
// @Tags Users
func (app *appContext) UnlinkDiscord(gc *gin.Context) {
var req forUserDTO
gc.BindJSON(&req)
/* user, status, err := app.jf.UserByID(req.ID, false)
if req.ID == "" || status != 200 || err != nil {
respond(400, "User not found", gc)
return
} */
delete(app.storage.discord, req.ID)
app.storage.storeDiscordUsers()
respondBool(200, true, gc)
}
// @Summary unlink a Telegram account from a Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
// @Router /users/telegram [delete]
// @Tags Users
func (app *appContext) UnlinkTelegram(gc *gin.Context) {
var req forUserDTO
gc.BindJSON(&req)
/* user, status, err := app.jf.UserByID(req.ID, false)
if req.ID == "" || status != 200 || err != nil {
respond(400, "User not found", gc)
return
} */
delete(app.storage.telegram, req.ID)
app.storage.storeTelegramUsers()
respondBool(200, true, gc)
}
// @Summary unlink a Matrix account from a Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Param forUserDTO body forUserDTO true "User's Jellyfin ID."
// @Router /users/matrix [delete]
// @Tags Users
func (app *appContext) UnlinkMatrix(gc *gin.Context) {
var req forUserDTO
gc.BindJSON(&req)
/* user, status, err := app.jf.UserByID(req.ID, false)
if req.ID == "" || status != 200 || err != nil {
respond(400, "User not found", gc)
return
} */
delete(app.storage.matrix, req.ID)
app.storage.storeMatrixUsers()
respondBool(200, true, gc)
}

119
api-ombi.go Normal file
View File

@@ -0,0 +1,119 @@
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func (app *appContext) getOmbiUser(jfID string) (map[string]interface{}, int, error) {
ombiUsers, code, err := app.ombi.GetUsers()
if err != nil || code != 200 {
return nil, code, err
}
jfUser, code, err := app.jf.UserByID(jfID, false)
if err != nil || code != 200 {
return nil, code, err
}
username := jfUser.Name
email := ""
if e, ok := app.storage.emails[jfID]; ok {
email = e.Addr
}
for _, ombiUser := range ombiUsers {
ombiAddr := ""
if a, ok := ombiUser["emailAddress"]; ok && a != nil {
ombiAddr = a.(string)
}
if ombiUser["userName"].(string) == username || (ombiAddr == email && email != "") {
return ombiUser, code, err
}
}
return nil, 400, fmt.Errorf("Couldn't find user")
}
// @Summary Get a list of Ombi users.
// @Produce json
// @Success 200 {object} ombiUsersDTO
// @Failure 500 {object} stringResponse
// @Router /ombi/users [get]
// @Security Bearer
// @tags Ombi
func (app *appContext) OmbiUsers(gc *gin.Context) {
app.debug.Println("Ombi users requested")
users, status, err := app.ombi.GetUsers()
if err != nil || status != 200 {
app.err.Printf("Failed to get users from Ombi (%d): %v", status, err)
respond(500, "Couldn't get users", gc)
return
}
userlist := make([]ombiUser, len(users))
for i, data := range users {
userlist[i] = ombiUser{
Name: data["userName"].(string),
ID: data["id"].(string),
}
}
gc.JSON(200, ombiUsersDTO{Users: userlist})
}
// @Summary Store Ombi user template in an existing profile.
// @Produce json
// @Param ombiUser body ombiUser true "User to source settings from"
// @Param profile path string true "Name of profile to store in"
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /profiles/ombi/{profile} [post]
// @Security Bearer
// @tags Ombi
func (app *appContext) SetOmbiProfile(gc *gin.Context) {
var req ombiUser
gc.BindJSON(&req)
profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName]
if !ok {
respondBool(400, false, gc)
return
}
template, code, err := app.ombi.TemplateByID(req.ID)
if err != nil || code != 200 || len(template) == 0 {
app.err.Printf("Couldn't get user from Ombi (%d): %v", code, err)
respond(500, "Couldn't get user", gc)
return
}
profile.Ombi = template
app.storage.profiles[profileName] = profile
if err := app.storage.storeProfiles(); err != nil {
respond(500, "Failed to store profile", gc)
app.err.Printf("Failed to store profiles: %v", err)
return
}
respondBool(204, true, gc)
}
// @Summary Remove ombi user template from a profile.
// @Produce json
// @Param profile path string true "Name of profile to store in"
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /profiles/ombi/{profile} [delete]
// @Security Bearer
// @tags Ombi
func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
profileName := gc.Param("profile")
profile, ok := app.storage.profiles[profileName]
if !ok {
respondBool(400, false, gc)
return
}
profile.Ombi = nil
app.storage.profiles[profileName] = profile
if err := app.storage.storeProfiles(); err != nil {
respond(500, "Failed to store profile", gc)
app.err.Printf("Failed to store profiles: %v", err)
return
}
respondBool(204, true, gc)
}

121
api-profiles.go Normal file
View File

@@ -0,0 +1,121 @@
package main
import (
"time"
"github.com/gin-gonic/gin"
)
// @Summary Get a list of profiles
// @Produce json
// @Success 200 {object} getProfilesDTO
// @Router /profiles [get]
// @Security Bearer
// @tags Profiles & Settings
func (app *appContext) GetProfiles(gc *gin.Context) {
app.storage.loadProfiles()
app.debug.Println("Profiles requested")
out := getProfilesDTO{
DefaultProfile: app.storage.defaultProfile,
Profiles: map[string]profileDTO{},
}
for name, p := range app.storage.profiles {
out.Profiles[name] = profileDTO{
Admin: p.Admin,
LibraryAccess: p.LibraryAccess,
FromUser: p.FromUser,
Ombi: p.Ombi != nil,
}
}
gc.JSON(200, out)
}
// @Summary Set the default profile to use.
// @Produce json
// @Param profileChangeDTO body profileChangeDTO true "Default profile object"
// @Success 200 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /profiles/default [post]
// @Security Bearer
// @tags Profiles & Settings
func (app *appContext) SetDefaultProfile(gc *gin.Context) {
req := profileChangeDTO{}
gc.BindJSON(&req)
app.info.Printf("Setting default profile to \"%s\"", req.Name)
if _, ok := app.storage.profiles[req.Name]; !ok {
app.err.Printf("Profile not found: \"%s\"", req.Name)
respond(500, "Profile not found", gc)
return
}
for name, profile := range app.storage.profiles {
if name == req.Name {
profile.Admin = true
app.storage.profiles[name] = profile
} else {
profile.Admin = false
}
}
app.storage.defaultProfile = req.Name
respondBool(200, true, gc)
}
// @Summary Create a profile based on a Jellyfin user's settings.
// @Produce json
// @Param newProfileDTO body newProfileDTO true "New profile object"
// @Success 200 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /profiles [post]
// @Security Bearer
// @tags Profiles & Settings
func (app *appContext) CreateProfile(gc *gin.Context) {
app.info.Println("Profile creation requested")
var req newProfileDTO
gc.BindJSON(&req)
app.jf.CacheExpiry = time.Now()
user, status, err := app.jf.UserByID(req.ID, false)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get user from Jellyfin (%d): %v", status, err)
respond(500, "Couldn't get user", gc)
return
}
profile := Profile{
FromUser: user.Name,
Policy: user.Policy,
}
app.debug.Printf("Creating profile from user \"%s\"", user.Name)
if req.Homescreen {
profile.Configuration = user.Configuration
profile.Displayprefs, status, err = app.jf.GetDisplayPreferences(req.ID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Failed to get DisplayPrefs (%d): %v", status, err)
respond(500, "Couldn't get displayprefs", gc)
return
}
}
app.storage.loadProfiles()
app.storage.profiles[req.Name] = profile
app.storage.storeProfiles()
app.storage.loadProfiles()
respondBool(200, true, gc)
}
// @Summary Delete an existing profile
// @Produce json
// @Param profileChangeDTO body profileChangeDTO true "Delete profile object"
// @Success 200 {object} boolResponse
// @Router /profiles [delete]
// @Security Bearer
// @tags Profiles & Settings
func (app *appContext) DeleteProfile(gc *gin.Context) {
req := profileChangeDTO{}
gc.BindJSON(&req)
name := req.Name
if _, ok := app.storage.profiles[name]; ok {
if app.storage.defaultProfile == name {
app.storage.defaultProfile = ""
}
delete(app.storage.profiles, name)
}
app.storage.storeProfiles()
respondBool(200, true, gc)
}

1195
api-users.go Normal file

File diff suppressed because it is too large Load Diff

2249
api.go

File diff suppressed because it is too large Load Diff

13
args.go
View File

@@ -75,18 +75,23 @@ func (app *appContext) loadArgs(firstCall bool) {
os.Setenv("JFA_DATAPATH", app.dataPath)
}
/* Adds start/stop/systemd to help message, and
/*
Adds start/stop/systemd to help message, and
also gets rid of usage for shorthand flags, and merge them with the full-length one.
implementation is 🤢, will clean this up eventually.
-h SHORTHAND
-help
prints this message.
becomes:
-help, -h
prints this message.
*/
func helpFunc() {
fmt.Fprint(os.Stderr, `Usage of jfa-go:
fmt.Fprint(stderr, `Usage of jfa-go:
start
start jfa-go as a daemon and run in the background.
stop
@@ -99,7 +104,7 @@ func helpFunc() {
// Write defaults into buffer then remove any shorthands
flag.CommandLine.SetOutput(&b)
flag.PrintDefaults()
flag.CommandLine.SetOutput(os.Stderr)
flag.CommandLine.SetOutput(stderr)
scanner := bufio.NewScanner(&b)
out := ""
line := scanner.Text()
@@ -150,5 +155,5 @@ func helpFunc() {
lastLine = true
}
}
fmt.Fprint(os.Stderr, out)
fmt.Fprint(stderr, out)
}

12
auth.go
View File

@@ -100,7 +100,7 @@ type getTokenDTO struct {
}
// @Summary Grabs an API token using username & password.
// @description Click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and set the API key as "Bearer `your api key`".
// @description If viewing docs locally, click the lock icon next to this, login with your normal jfa-go credentials. Click 'try it out', then 'execute' and an API Key will be returned, copy it (not including quotes). On any of the other routes, click the lock icon and set the API key as "Bearer `your api key`".
// @Produce json
// @Success 200 {object} getTokenDTO
// @Failure 401 {object} stringResponse
@@ -145,8 +145,14 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
return
}
jfID = user.ID
if app.config.Section("ui").Key("admin_only").MustBool(true) {
if !user.Policy.IsAdministrator {
if !app.config.Section("ui").Key("allow_all").MustBool(false) {
accountsAdmin := false
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
if emailStore, ok := app.storage.emails[jfID]; ok {
accountsAdmin = emailStore.Admin
}
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
if !accountsAdmin {
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", creds[0])
respond(401, "Unauthorized", gc)
return

View File

@@ -39,6 +39,8 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("jellyfin", "public_server", app.config.Section("jellyfin").Key("server").String())
app.MustSetValue("ui", "redirect_url", app.config.Section("jellyfin").Key("public_server").String())
for _, key := range app.config.Section("files").Keys() {
if name := key.Name(); name != "html_templates" && name != "lang_files" {
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
@@ -71,6 +73,12 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("deletion", "email_html", "jfa-go:"+"deleted.html")
app.MustSetValue("deletion", "email_text", "jfa-go:"+"deleted.txt")
app.MustSetValue("smtp", "hello_hostname", "localhost")
app.MustSetValue("smtp", "cert_validation", "true")
sc := app.config.Section("discord").Key("start_command").MustString("start")
app.config.Section("discord").Key("start_command").SetValue(strings.TrimPrefix(strings.TrimPrefix(sc, "/"), "!"))
jfUrl := app.config.Section("jellyfin").Key("server").String()
if !(strings.HasPrefix(jfUrl, "http://") || strings.HasPrefix(jfUrl, "https://")) {
app.config.Section("jellyfin").Key("server").SetValue("http://" + jfUrl)
@@ -93,6 +101,11 @@ func (app *appContext) loadConfig() error {
app.MustSetValue("user_expiry", "email_text", "jfa-go:"+"user-expired.txt")
app.MustSetValue("matrix", "topic", "Jellyfin notifications")
app.MustSetValue("matrix", "show_on_reg", "true")
app.MustSetValue("discord", "show_on_reg", "true")
app.MustSetValue("telegram", "show_on_reg", "true")
app.config.Section("jellyfin").Key("version").SetValue(version)
app.config.Section("jellyfin").Key("device").SetValue("jfa-go")
@@ -149,6 +162,11 @@ func (app *appContext) loadConfig() error {
substituteStrings = app.config.Section("jellyfin").Key("substitute_jellyfin_strings").MustString("")
if substituteStrings != "" {
v := app.config.Section("ui").Key("success_message")
v.SetValue(strings.ReplaceAll(v.String(), "Jellyfin", substituteStrings))
}
oldFormLang := app.config.Section("ui").Key("language").MustString("")
if oldFormLang != "" {
app.storage.lang.chosenFormLang = oldFormLang

View File

@@ -181,6 +181,15 @@
"value": true,
"description": "Allows only admin users on Jellyfin to access the admin page."
},
"allow_all": {
"name": "Allow all users to login",
"required": false,
"requires_restart": true,
"depends_true": "jellyfin_login",
"type": "bool",
"value": false,
"description": "Allow all Jellyfin users to access jfa-go. Not recommended, add individual users in the Accounts tab instead."
},
"username": {
"name": "Web Username",
"required": true,
@@ -241,12 +250,30 @@
"description": "Displayed when a user creates an account"
},
"url_base": {
"name": "URL Base",
"name": "Reverse Proxy subfolder",
"required": false,
"requires_restart": true,
"type": "text",
"value": "",
"description": "URL base for when running jfa-go with a reverse proxy in a subfolder."
"description": "URL base for when running jfa-go with a reverse proxy in a subfolder. include preceding /, e.g \"/accounts\"."
},
"redirect_url": {
"name": "Form success redirect URL",
"required": false,
"requires_restart": false,
"type": "text",
"value": "",
"advanced": true,
"description": "Set a different URL for the sign-up form to redirect the user to when they've signed up. Default to 'Public Server' or 'Server' in the Jellyfin tab."
},
"auto_redirect": {
"name": "Auto redirect on success",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"advanced": true,
"description": "Navigate directly to the above URL instead of needing the user to click \"Continue\"."
}
}
},
@@ -294,6 +321,23 @@
}
}
},
"captcha": {
"order": [],
"meta": {
"name": "Captcha",
"description": "Settings related to user creation CAPTCHAs."
},
"settings": {
"enabled": {
"name": "Enabled",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Enable a CAPTCHA on the account creation form."
}
}
},
"password_validation": {
"order": [],
"meta": {
@@ -458,6 +502,23 @@
"type": "bool",
"value": false,
"description": "Send emails as plain text instead of HTML."
},
"required": {
"name": "Require on sign-up",
"required": false,
"requires_restart": false,
"depends_true": "method",
"type": "bool",
"value": false,
"description": "Require an email address on sign-up."
},
"require_unique": {
"name": "Require unique address",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Disables using the same address on multiple accounts."
}
}
},
@@ -535,6 +596,15 @@
"type": "password",
"value": "smtp password"
},
"hello_hostname": {
"name": "HELLO Hostname",
"required": false,
"requires_restart": false,
"advanced": true,
"type": "text",
"value": "localhost",
"description": "Hostname sent when sending HELLO to the SMTP server. Some servers don't like the default \"localhost\" value, such as smtp-relay.gmail.com."
},
"ssl_cert": {
"name": "Path to custom SSL certificate",
"required": false,
@@ -543,6 +613,15 @@
"type": "text",
"value": "",
"description": "Use if your SMTP server's SSL Certificate is not trusted by the system."
},
"cert_validation": {
"name": "Verify certificate",
"required": false,
"requires_restart": false,
"advanced": true,
"type": "bool",
"value": true,
"description": "Warning, disabling this makes you much more vulnerable to man-in-the-middle attacks"
}
}
},
@@ -561,15 +640,32 @@
"value": false,
"description": "Enable signup verification through Discord and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
},
"show_on_reg": {
"name": "Show on user registration",
"required": false,
"requires_restart": true,
"type": "bool",
"depends_true": "enabled",
"value": true,
"description": "Allow users to link their Discord on the registration page."
},
"required": {
"name": "Require on sign-up",
"required": false,
"required_restart": true,
"requires_restart": true,
"depends_true": "enabled",
"type": "bool",
"value": false,
"description": "Require Discord connection on sign-up. See the jfa-go wiki for info on setting this up."
},
"require_unique": {
"name": "Require unique user",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Disables using the same user on multiple Jellyfin accounts."
},
"token": {
"name": "API Token",
"required": false,
@@ -585,7 +681,7 @@
"requires_restart": true,
"depends_true": "enabled",
"type": "text",
"value": "!start",
"value": "start",
"description": "Command to start the user verification process."
},
"channel": {
@@ -615,6 +711,18 @@
"value": "",
"description": "Channel to invite new users to."
},
"apply_role": {
"name": "Apply Role on connection",
"required": false,
"requires_restart": true,
"depends_true": "enabled",
"type": "select",
"options": [
["", "None"]
],
"value": "",
"description": "Add the selected role to a user when they sign up."
},
"language": {
"name": "Language",
"required": false,
@@ -644,6 +752,15 @@
"value": false,
"description": "Enable signup verification through Telegram and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
},
"show_on_reg": {
"name": "Show on user registration",
"required": false,
"requires_restart": true,
"type": "bool",
"depends_true": "enabled",
"value": true,
"description": "Allow users to link their Telegram on the registration page."
},
"required": {
"name": "Require on sign-up",
"required": false,
@@ -653,6 +770,14 @@
"value": false,
"description": "Require telegram connection on sign-up."
},
"require_unique": {
"name": "Require unique user",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Disables using the same user on multiple Jellyfin accounts."
},
"token": {
"name": "API Token",
"required": false,
@@ -691,6 +816,15 @@
"value": false,
"description": "Enable signup verification through Matrix and the sending of notifications through it.\nSee the jfa-go wiki for setting up a bot."
},
"show_on_reg": {
"name": "Show on user registration",
"required": false,
"requires_restart": true,
"type": "bool",
"depends_true": "enabled",
"value": true,
"description": "Allow users to link their Matrix on the registration page."
},
"required": {
"name": "Require on sign-up",
"required": false,
@@ -700,6 +834,14 @@
"value": false,
"description": "Require Matrix connection on sign-up."
},
"require_unique": {
"name": "Require unique user",
"required": false,
"requires_restart": true,
"type": "bool",
"value": false,
"description": "Disables using the same user on multiple Jellyfin accounts."
},
"homeserver": {
"name": "Home Server URL",
"required": false,
@@ -1002,7 +1144,7 @@
"order": [],
"meta": {
"name": "Ombi Integration",
"description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to create a user template for this to work. Once enabled, refresh to see an option in settings for this. To handle password resets for Ombi & Jellyfin, enable \"Use reset link instead of PIN\"."
"description": "Connect to Ombi to automatically create both Ombi and Jellyfin accounts for new users. You'll need to add a ombi template to an existing User Profile for accounts to be created, which you can do by refreshing then checking Settings > User Profiles. To handle password resets for Ombi & Jellyfin, enable \"Use reset link instead of PIN\"."
},
"settings": {
"enabled": {

View File

@@ -1,10 +1,13 @@
@import "../node_modules/a17t/dist/a17t.css";
@import "remixicon.css";
@import "./modal.css";
@import "./dark.css";
@import "./tooltip.css";
@import "./loader.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--border-width-default: 2px;
--border-width-2: 3px;
@@ -12,14 +15,48 @@
--border-width-8: 8px;
}
.light-theme {
.light {
--settings-section-button-filter: 90%;
}
.body {
.dark {
--settings-section-button-filter: 80%;
}
.dark body {
background-color: #101010;
}
html:not(.dark) body {
background-color: #fff;
}
.dark select, .dark option, .dark input {
background: #202020;
}
html:not(.dark) .card.\@low:not(.\~neutral):not(.\~positive):not(.\~urge):not(.\~warning):not(.\~info):not(.\~critical),
html:not(.dark) .card.\@low:not(.\~neutral):not(.\~positive):not(.\~urge):not(.\~warning):not(.\~info):not(.\~critical) > * {
/* Colors from ~neutral */
--color-fill-high: #64748b;
--color-fill-low: #e2e8f0;
--color-content-high: #f8fafc;
--color-content-low: #1e293b;
--color-accent-high: #475569;
--color-accent-low: #cbd5e1;
--color-muted-high: #475569;
--color-muted-low: #f1f5f9;
background-color: #fff;
}
.light-only {
display: none;
}
.dark-only {
display: initial;
}
.page-container {
margin: 5% 20% 5% 20%;
}
@@ -27,6 +64,7 @@
@media (max-width: 1100px) {
.page-container {
margin: 2%;
margin-top: 5rem;
}
}
@@ -50,6 +88,8 @@
.banner.header {
margin-bottom: var(--spacing-4,1rem);
max-width: calc(100% + 2.2rem); /* no idea why this works */
margin-left: -1.1rem;
}
.banner.footer {
@@ -57,11 +97,6 @@
padding: var(--spacing-4,1rem);
}
.modal-content .banner {
margin-left: calc(-1 * var(--spacing-4,1rem) - 0.5%); /* Not sure why this is necessary */
margin-right: calc(-1 * var(--spacing-4,1rem) - 0.5%);
}
div.card:contains(section.banner.footer) {
padding-bottom: 0px;
}
@@ -70,54 +105,6 @@ div.card:contains(section.banner.footer) {
font-size: 2rem;
}
.mb-half {
margin-bottom: 0.5rem;
}
.mb-1 {
margin-bottom: 1rem;
}
.mb-2 {
margin-bottom: 2rem;
}
.mt-half {
margin-top: 0.5rem;
}
.mt-1 {
margin-top: 1rem;
}
.ml-1 {
margin-left: 1rem;
}
.ml-half {
margin-left: 0.5rem;
}
.mr-half {
margin-right: 0.5rem;
}
.mr-1 {
margin-right: 1rem;
}
.p-1 {
padding: 1rem;
}
.pb-1 {
padding-bottom: 1rem;
}
.pl-1 {
padding-left: 1rem;
}
.al {
text-align: left;
}
@@ -146,20 +133,11 @@ div.card:contains(section.banner.footer) {
align-items: top;
}
.flex {
display: flex;
}
.flex-expand {
display: flex;
justify-content: space-between;
}
.flex-row {
display: flex;
flex-direction: row;
}
.flex-row-group {
display: block;
flex-grow: 1;
@@ -194,6 +172,18 @@ span.sm:not(.heading) {
flex-direction: column;
}
.flex-form {
display: flex;
flex-direction: column;
}
@media screen and (min-width: 768px) {
.flex-form {
flex: 1;
margin: 0.5rem;
}
}
@media screen and (max-width: 400px) {
.row {
flex-direction: column;
@@ -203,15 +193,6 @@ span.sm:not(.heading) {
}
}
.fr {
float: right;
}
.monospace {
background-color: inherit; /* so we can use a17t code blocks */
font-family: Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;
}
sup.\~critical, .text-critical {
color: var(--color-critical-normal-content);
}
@@ -330,7 +311,7 @@ sup.\~critical, .text-critical {
padding-left: 0px;
}
.block {
.block {
display: block;
}
@@ -377,10 +358,8 @@ sup.\~critical, .text-critical {
}
.settings-section-button {
box-sizing: border-box;
width: 100%;
height: 2.5rem;
background-color: rgba(0,0,0,0);
}
.settings-section-button:hover, .settings-section-button:focus {
@@ -392,8 +371,6 @@ sup.\~critical, .text-critical {
}
.settings-section-button.selected {
background-color: var(--color-neutral-normal-fill);
--buton-filter-brightness: var(--settings-section-button-filter);
filter: brightness(var(--settings-section-button-filter)) !important;
}
@@ -421,11 +398,21 @@ select, textarea {
-moz-appearance: none;
}
html.dark textarea {
background-color: #202020
}
input {
color: inherit;
border: 0 solid var(--color-neutral-300);
}
table {
color: var(--color-content);
}
p.top {
margin-top: 0px;
}
@@ -447,16 +434,27 @@ p.top {
margin-bottom: -0.5rem;
}
.dropdown.over-top {
position: absolute;
}
.dropdown-display.lg {
white-space: nowrap;
}
.dropdown-display.above {
top: auto;
bottom: 115%;
}
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
background-color: var(--color-content-high) !important;
overflow-x: scroll;
}
.circle {
@@ -492,10 +490,10 @@ a:hover:not(.lang-link):not(.\~urge), a:active:not(.lang-link):not(.\~urge) {
text-align: center;
}
.search {
/* .search {
max-width: 15rem;
min-width: 10rem;
}
} */
td.img-circle {
width: 32px;
@@ -537,3 +535,28 @@ div.card:contains(section.banner.footer) {
.card.sectioned .section {
padding: var(--spacing-4, 1rem);
}
.button.discord.\@low {
background-color: rgba(88, 101, 242,60%);
}
.button.discord.\@low:not(.lang-link) {
color: rgba(38, 51, 192, 90%);
}
.pb-0i {
padding-bottom: 0px !important
}
.mx-0i {
margin-left: 0px !important;
margin-right: 0px !important
}
.text-center-i {
text-align: center !important;
}
input[type="checkbox" i], [class^="ri-"], [class*=" ri-"], .ri-refresh-line:before, .modal-close {
cursor: pointer;
}

View File

@@ -1,91 +0,0 @@
.dark-theme {
--settings-section-button-filter: 110%;
--color-neutral-900: rgba(255, 255, 255, 0.87);
--color-neutral-800: rgba(255, 255, 255, 0.8);
--color-neutral-700: rgba(255, 255, 255, 0.73);
--color-neutral-600: rgba(255, 255, 255, 0.66);
--color-neutral-500: rgb(153, 153, 153);
--color-neutral-400: #383838;
--color-neutral-300: #303030;
--color-neutral-200: #292929;
--color-neutral-100: #242424;
--color-neutral-50: #202020;
--color-neutral-000: #101010;
--color-critical-900: #fef2f2;
--color-critical-800: #fee2e2;
--color-critical-700: #fecaca;
--color-critical-600: #fca5a5;
--color-critical-500: #f87171;
--color-critical-400: #ef4444;
--color-critical-300: #dc2626;
--color-critical-200: #b91c1c;
--color-critical-100: #991b1b;
--color-critical-50: #7f1d1d;
--color-critical-000: #441313;
--color-warning-900: #fffbeb;
--color-warning-800: #fef3c7;
--color-warning-700: #fde68a;
--color-warning-600: #fcd34d;
--color-warning-500: #fbbf24;
--color-warning-400: #f59e0b;
--color-warning-300: #d97706;
--color-warning-200: #b45309;
--color-warning-100: #92400e;
--color-warning-50: #783900;
--color-warning-000: #411e01;
--color-positive-900: #f0fdf4;
--color-positive-800: #dcfce7;
--color-positive-700: #bbf7d0;
--color-positive-600: #86efac;
--color-positive-500: #4ade80;
--color-positive-400: #22c55e;
--color-positive-300: #16a34a;
--color-positive-200: #15803d;
--color-positive-100: #166534;
--color-positive-50: #14532d;
--color-positive-000: #0f2e1b;
--color-urge-900: #e0ffff;
--color-urge-800: #c0fbff;
--color-urge-700: #a0f4ff;
--color-urge-600: #80e9ff;
--color-urge-500: #60dbfb;
--color-urge-400: #40cbf3;
--color-urge-300: #20b9e9;
--color-urge-200: #00a4dc; /* tab buttons */
--color-urge-100: #0054bc;
--color-urge-50: #00169a;
--color-urge-000: #050076;
--color-info-900: #f5f3ff;
--color-info-800: #ede9fe;
--color-info-700: #ddd6fe;
--color-info-600: #c4b5fd;
--color-info-500: #a78bfa;
--color-info-400: #8b5cf6;
--color-info-300: #7c3aed;
--color-info-200: #6d28d9;
--color-info-100: #5b21b6;
--color-info-50: #4c1d95;
--color-info-000: #240e44;
--color-neutral-normal-content: #ffffff;
}
.light-only {
display: none;
}
.dark-only {
display: initial;
}
.dark-theme select option {
background: #202020;
}

351
css/dark.js Normal file
View File

@@ -0,0 +1,351 @@
var c = {
inherit: 'inherit',
current: 'currentColor',
transparent: 'transparent',
black: '#000',
white: '#fff',
d_neutral: {
900: "rgba(255, 255, 255, 0.87)",
800: "rgba(255, 255, 255, 0.8)",
700: "rgba(255, 255, 255, 0.73)",
600: "rgba(255, 255, 255, 0.66)",
500: "rgb(153, 153, 153)",
400: "#383838",
300: "#303030",
200: "#292929",
100: "#242424",
50: "#202020",
000: "#101010"
},
d_critical: {
900: "#fef2f2",
800: "#fee2e2",
700: "#fecaca",
600: "#fca5a5",
500: "#f87171",
400: "#ef4444",
300: "#dc2626",
200: "#b91c1c",
100: "#991b1b",
50: "#7f1d1d",
000: "#441313"
},
d_warning: {
900: "#fffbeb",
800: "#fef3c7",
700: "#fde68a",
600: "#fcd34d",
500: "#fbbf24",
400: "#f59e0b",
300: "#d97706",
200: "#b45309",
100: "#92400e",
50: "#783900",
000: "#411e01"
},
d_positive: {
900: "#f0fdf4",
800: "#dcfce7",
700: "#bbf7d0",
600: "#86efac",
500: "#4ade80",
400: "#22c55e",
300: "#16a34a",
200: "#15803d",
100: "#166534",
50: "#14532d",
000: "#0f2e1b"
},
d_urge: {
900: "#e0ffff",
800: "#c0fbff",
700: "#a0f4ff",
600: "#80e9ff",
500: "#60dbfb",
400: "#40cbf3",
300: "#20b9e9",
200: "#00a4dc",
100: "#0054bc",
50: "#00169a",
000: "#050076"
},
d_info: {
900: "#f5f3ff",
800: "#ede9fe",
700: "#ddd6fe",
600: "#c4b5fd",
500: "#a78bfa",
400: "#8b5cf6",
300: "#7c3aed",
200: "#6d28d9",
100: "#5b21b6",
50: "#4c1d95",
000: "#240e44"
},
slate: {
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a'
},
gray: {
50: '#f9fafb',
100: '#f3f4f6',
200: '#e5e7eb',
300: '#d1d5db',
400: '#9ca3af',
500: '#6b7280',
600: '#4b5563',
700: '#374151',
800: '#1f2937',
900: '#111827'
},
zinc: {
50: '#fafafa',
100: '#f4f4f5',
200: '#e4e4e7',
300: '#d4d4d8',
400: '#a1a1aa',
500: '#71717a',
600: '#52525b',
700: '#3f3f46',
800: '#27272a',
900: '#18181b'
},
neutral: {
50: '#fafafa',
100: '#f5f5f5',
200: '#e5e5e5',
300: '#d4d4d4',
400: '#a3a3a3',
500: '#737373',
600: '#525252',
700: '#404040',
800: '#262626',
900: '#171717'
},
stone: {
50: '#fafaf9',
100: '#f5f5f4',
200: '#e7e5e4',
300: '#d6d3d1',
400: '#a8a29e',
500: '#78716c',
600: '#57534e',
700: '#44403c',
800: '#292524',
900: '#1c1917'
},
red: {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d'
},
orange: {
50: '#fff7ed',
100: '#ffedd5',
200: '#fed7aa',
300: '#fdba74',
400: '#fb923c',
500: '#f97316',
600: '#ea580c',
700: '#c2410c',
800: '#9a3412',
900: '#7c2d12'
},
amber: {
50: '#fffbeb',
100: '#fef3c7',
200: '#fde68a',
300: '#fcd34d',
400: '#fbbf24',
500: '#f59e0b',
600: '#d97706',
700: '#b45309',
800: '#92400e',
900: '#78350f'
},
yellow: {
50: '#fefce8',
100: '#fef9c3',
200: '#fef08a',
300: '#fde047',
400: '#facc15',
500: '#eab308',
600: '#ca8a04',
700: '#a16207',
800: '#854d0e',
900: '#713f12'
},
lime: {
50: '#f7fee7',
100: '#ecfccb',
200: '#d9f99d',
300: '#bef264',
400: '#a3e635',
500: '#84cc16',
600: '#65a30d',
700: '#4d7c0f',
800: '#3f6212',
900: '#365314'
},
green: {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d'
},
emerald: {
50: '#ecfdf5',
100: '#d1fae5',
200: '#a7f3d0',
300: '#6ee7b7',
400: '#34d399',
500: '#10b981',
600: '#059669',
700: '#047857',
800: '#065f46',
900: '#064e3b'
},
teal: {
50: '#f0fdfa',
100: '#ccfbf1',
200: '#99f6e4',
300: '#5eead4',
400: '#2dd4bf',
500: '#14b8a6',
600: '#0d9488',
700: '#0f766e',
800: '#115e59',
900: '#134e4a'
},
cyan: {
50: '#ecfeff',
100: '#cffafe',
200: '#a5f3fc',
300: '#67e8f9',
400: '#22d3ee',
500: '#06b6d4',
600: '#0891b2',
700: '#0e7490',
800: '#155e75',
900: '#164e63'
},
sky: {
50: '#f0f9ff',
100: '#e0f2fe',
200: '#bae6fd',
300: '#7dd3fc',
400: '#38bdf8',
500: '#0ea5e9',
600: '#0284c7',
700: '#0369a1',
800: '#075985',
900: '#0c4a6e'
},
blue: {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a'
},
indigo: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81'
},
violet: {
50: '#f5f3ff',
100: '#ede9fe',
200: '#ddd6fe',
300: '#c4b5fd',
400: '#a78bfa',
500: '#8b5cf6',
600: '#7c3aed',
700: '#6d28d9',
800: '#5b21b6',
900: '#4c1d95'
},
purple: {
50: '#faf5ff',
100: '#f3e8ff',
200: '#e9d5ff',
300: '#d8b4fe',
400: '#c084fc',
500: '#a855f7',
600: '#9333ea',
700: '#7e22ce',
800: '#6b21a8',
900: '#581c87'
},
fuchsia: {
50: '#fdf4ff',
100: '#fae8ff',
200: '#f5d0fe',
300: '#f0abfc',
400: '#e879f9',
500: '#d946ef',
600: '#c026d3',
700: '#a21caf',
800: '#86198f',
900: '#701a75'
},
pink: {
50: '#fdf2f8',
100: '#fce7f3',
200: '#fbcfe8',
300: '#f9a8d4',
400: '#f472b6',
500: '#ec4899',
600: '#db2777',
700: '#be185d',
800: '#9d174d',
900: '#831843'
},
rose: {
50: '#fff1f2',
100: '#ffe4e6',
200: '#fecdd3',
300: '#fda4af',
400: '#fb7185',
500: '#f43f5e',
600: '#e11d48',
700: '#be123c',
800: '#9f1239',
900: '#881337'
}
}
module.exports = c;

View File

@@ -1,6 +1,6 @@
.loader {
height: auto;
color: rgba(0, 0, 0, 0);
color: rgba(0, 0, 0, 0) !important;
}
.loader .dot {

View File

@@ -10,56 +10,6 @@
background-color: rgba(0,0,0,40%);
}
.modal-shown {
display: block;
}
@keyframes modal-hide {
from { opacity: 1; }
to { opacity: 0; }
}
.modal-hiding {
animation: modal-hide 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
@keyframes modal-content-show {
from {
opacity: 0;
top: -6rem;
}
to {
opacity: 1;
top: 0;
}
}
.modal-content {
position: relative;
margin: 10% auto;
width: 30%;
}
.modal-content.wide {
width: 60%;
}
.modal-shown .modal-content {
animation: modal-content-show 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
}
@media screen and (max-width: 1000px) {
.modal-content.wide {
width: 75%;
}
}
@media screen and (max-width: 400px) {
.modal-content, .modal-content.wide {
width: 90%;
}
}
.modal-close {
float: right;
color: #aaa;

160
daemon.go Normal file
View File

@@ -0,0 +1,160 @@
package main
import "time"
// clearEmails removes stored emails for users which no longer exist.
// meant to be called with other such housekeeping functions, so assumes
// the user cache is fresh.
func (app *appContext) clearEmails() {
app.debug.Println("Housekeeping: removing unused email addresses")
users, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil || len(users) == 0 {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
return
}
// Rebuild email storage to from existing users to reduce time complexity
emails := map[string]EmailAddress{}
for _, user := range users {
if email, ok := app.storage.emails[user.ID]; ok {
emails[user.ID] = email
}
}
app.storage.emails = emails
app.storage.storeEmails()
}
// clearDiscord does the same as clearEmails, but for Discord Users.
func (app *appContext) clearDiscord() {
app.debug.Println("Housekeeping: removing unused Discord IDs")
users, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil || len(users) == 0 {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
return
}
// Rebuild discord storage to from existing users to reduce time complexity
dcUsers := map[string]DiscordUser{}
for _, user := range users {
if dcUser, ok := app.storage.discord[user.ID]; ok {
dcUsers[user.ID] = dcUser
}
}
app.storage.discord = dcUsers
app.storage.storeDiscordUsers()
}
// clearMatrix does the same as clearEmails, but for Matrix Users.
func (app *appContext) clearMatrix() {
app.debug.Println("Housekeeping: removing unused Matrix IDs")
users, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil || len(users) == 0 {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
return
}
// Rebuild matrix storage to from existing users to reduce time complexity
mxUsers := map[string]MatrixUser{}
for _, user := range users {
if mxUser, ok := app.storage.matrix[user.ID]; ok {
mxUsers[user.ID] = mxUser
}
}
app.storage.matrix = mxUsers
app.storage.storeMatrixUsers()
}
// clearTelegram does the same as clearEmails, but for Telegram Users.
func (app *appContext) clearTelegram() {
app.debug.Println("Housekeeping: removing unused Telegram IDs")
users, status, err := app.jf.GetUsers(false)
if status != 200 || err != nil || len(users) == 0 {
app.err.Printf("Failed to get users from Jellyfin (%d): %v", status, err)
return
}
// Rebuild telegram storage to from existing users to reduce time complexity
tgUsers := map[string]TelegramUser{}
for _, user := range users {
if tgUser, ok := app.storage.telegram[user.ID]; ok {
tgUsers[user.ID] = tgUser
}
}
app.storage.telegram = tgUsers
app.storage.storeTelegramUsers()
}
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
type housekeepingDaemon struct {
Stopped bool
ShutdownChannel chan string
Interval time.Duration
period time.Duration
jobs []func(app *appContext)
app *appContext
}
func newInviteDaemon(interval time.Duration, app *appContext) *housekeepingDaemon {
daemon := housekeepingDaemon{
Stopped: false,
ShutdownChannel: make(chan string),
Interval: interval,
period: interval,
app: app,
}
daemon.jobs = []func(app *appContext){func(app *appContext) {
app.debug.Println("Housekeeping: Checking for expired invites")
app.checkInvites()
}}
clearEmail := app.config.Section("email").Key("require_unique").MustBool(false)
clearDiscord := app.config.Section("discord").Key("require_unique").MustBool(false)
clearTelegram := app.config.Section("telegram").Key("require_unique").MustBool(false)
clearMatrix := app.config.Section("matrix").Key("require_unique").MustBool(false)
if clearEmail || clearDiscord || clearTelegram || clearMatrix {
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.jf.CacheExpiry = time.Now() })
}
if clearEmail {
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearEmails() })
}
if clearDiscord {
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearDiscord() })
}
if clearTelegram {
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearTelegram() })
}
if clearMatrix {
daemon.jobs = append(daemon.jobs, func(app *appContext) { app.clearMatrix() })
}
return &daemon
}
func (rt *housekeepingDaemon) run() {
rt.app.info.Println("Invite daemon started")
for {
select {
case <-rt.ShutdownChannel:
rt.ShutdownChannel <- "Down"
return
case <-time.After(rt.period):
break
}
started := time.Now()
rt.app.storage.loadInvites()
for _, job := range rt.jobs {
job(rt.app)
}
finished := time.Now()
duration := finished.Sub(started)
rt.period = rt.Interval - duration
}
}
func (rt *housekeepingDaemon) Shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel
close(rt.ShutdownChannel)
}

View File

@@ -18,7 +18,10 @@ type DiscordDaemon struct {
guildID string
serverChannelName, serverName string
users map[string]DiscordUser // Map of user IDs to users. Added to on first interaction, and loaded from app.storage.discord on start.
roleID string
app *appContext
commandHandlers map[string]func(s *dg.Session, i *dg.InteractionCreate, lang string)
commandIDs []string
}
func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
@@ -38,7 +41,13 @@ func newDiscordDaemon(app *appContext) (*DiscordDaemon, error) {
verifiedTokens: map[string]DiscordUser{},
users: map[string]DiscordUser{},
app: app,
roleID: app.config.Section("discord").Key("apply_role").String(),
commandHandlers: map[string]func(s *dg.Session, i *dg.InteractionCreate, lang string){},
commandIDs: []string{},
}
dd.commandHandlers[app.config.Section("discord").Key("start_command").MustString("start")] = dd.cmdStart
dd.commandHandlers["lang"] = dd.cmdLang
dd.commandHandlers["pin"] = dd.cmdPIN
for _, user := range app.storage.discord {
dd.users[user.ID] = user
}
@@ -72,6 +81,9 @@ func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string)
func (d *DiscordDaemon) run() {
d.bot.AddHandler(d.messageHandler)
d.bot.AddHandler(d.commandHandler)
d.bot.Identify.Intents = dg.IntentsGuildMessages | dg.IntentsDirectMessages | dg.IntentsGuildMembers | dg.IntentsGuildInvites
if err := d.bot.Open(); err != nil {
d.app.err.Printf("Discord: Failed to start daemon: %v", err)
@@ -105,12 +117,44 @@ func (d *DiscordDaemon) run() {
d.inviteChannelName = invChannel
}
}
defer d.deregisterCommands()
defer d.bot.Close()
go d.registerCommands()
<-d.ShutdownChannel
d.ShutdownChannel <- "Down"
return
}
// ListRoles returns a list of available (excluding bot and @everyone) roles in a guild as a list of containing an array of the guild ID and its name.
func (d *DiscordDaemon) ListRoles() (roles [][2]string, err error) {
var r []*dg.Role
r, err = d.bot.GuildRoles(d.guildID)
if err != nil {
d.app.err.Printf("Discord: Failed to get roles: %v", err)
return
}
for _, role := range r {
if role.Name != d.username && role.Name != "@everyone" {
roles = append(roles, [2]string{role.ID, role.Name})
}
}
// roles = make([][2]string, len(r))
// for i, role := range r {
// roles[i] = [2]string{role.ID, role.Name}
// }
return
}
// ApplyRole applies the member role to the given user if set.
func (d *DiscordDaemon) ApplyRole(userID string) error {
if d.roleID == "" {
return nil
}
return d.bot.GuildMemberRoleAdd(d.guildID, userID, d.roleID)
}
// NewTempInvite creates an invite link, and returns the invite URL, as well as the URL for the server icon.
func (d *DiscordDaemon) NewTempInvite(ageSeconds, maxUses int) (inviteURL, iconURL string) {
var inv *dg.Invite
@@ -166,10 +210,29 @@ func (d *DiscordDaemon) NewTempInvite(ageSeconds, maxUses int) (inviteURL, iconU
d.app.err.Printf("Discord: Failed to get guild: %v", err)
return
}
iconURL = guild.IconURL()
iconURL = guild.IconURL("256")
return
}
// RenderDiscordUsername returns String of discord username, with support for new discriminator-less versions.
func RenderDiscordUsername[DcUser *dg.User | DiscordUser](user DcUser) string {
u, ok := interface{}(user).(*dg.User)
var discriminator, username string
if ok {
discriminator = u.Discriminator
username = u.Username
} else {
u2 := interface{}(user).(DiscordUser)
discriminator = u2.Discriminator
username = u2.Username
}
if discriminator == "0" {
return "@" + username
}
return username + "#" + discriminator
}
// Returns the user(s) roughly corresponding to the username (if they are in the guild).
// if no discriminator (#xxxx) is given in the username and there are multiple corresponding users, a list of all matching users is returned.
func (d *DiscordDaemon) GetUsers(username string) []*dg.Member {
@@ -183,6 +246,10 @@ func (d *DiscordDaemon) GetUsers(username string) []*dg.Member {
return nil
}
hasDiscriminator := strings.Contains(username, "#")
hasAt := strings.HasPrefix(username, "@")
if hasAt {
username = username[1:]
}
var users []*dg.Member
for _, member := range members {
if hasDiscriminator {
@@ -190,6 +257,11 @@ func (d *DiscordDaemon) GetUsers(username string) []*dg.Member {
return []*dg.Member{member}
}
}
if hasAt {
if member.User.Username == username && member.User.Discriminator == "0" {
return []*dg.Member{member}
}
}
if strings.Contains(member.User.Username, username) {
users = append(users, member)
}
@@ -224,6 +296,208 @@ func (d *DiscordDaemon) Shutdown() {
close(d.ShutdownChannel)
}
func (d *DiscordDaemon) registerCommands() {
commands := []*dg.ApplicationCommand{
{
Name: d.app.config.Section("discord").Key("start_command").MustString("start"),
Description: "Start the Discord linking process. The bot will send further instructions.",
},
{
Name: "lang",
Description: "Set the language for the bot.",
Options: []*dg.ApplicationCommandOption{
{
Type: dg.ApplicationCommandOptionString,
Name: "language",
Description: "Language Name",
Required: true,
Choices: []*dg.ApplicationCommandOptionChoice{},
},
},
},
{
Name: "pin",
Description: "Send PIN for Discord verification.",
Options: []*dg.ApplicationCommandOption{
{
Type: dg.ApplicationCommandOptionString,
Name: "pin",
Description: "Verification PIN (e.g AB-CD-EF)",
Required: true,
},
},
},
}
commands[1].Options[0].Choices = make([]*dg.ApplicationCommandOptionChoice, len(d.app.storage.lang.Telegram))
i := 0
for code := range d.app.storage.lang.Telegram {
commands[1].Options[0].Choices[i] = &dg.ApplicationCommandOptionChoice{
Name: d.app.storage.lang.Telegram[code].Meta.Name,
Value: code,
}
i++
}
// d.deregisterCommands()
d.commandIDs = make([]string, len(commands))
// cCommands, err := d.bot.ApplicationCommandBulkOverwrite(d.bot.State.User.ID, d.guildID, commands)
// if err != nil {
// d.app.err.Printf("Discord: Cannot create commands: %v", err)
// }
for i, cmd := range commands {
command, err := d.bot.ApplicationCommandCreate(d.bot.State.User.ID, d.guildID, cmd)
if err != nil {
d.app.err.Printf("Discord: Cannot create command \"%s\": %v", cmd.Name, err)
} else {
d.app.debug.Printf("Discord: registered command \"%s\"", cmd.Name)
d.commandIDs[i] = command.ID
}
}
}
func (d *DiscordDaemon) deregisterCommands() {
existingCommands, err := d.bot.ApplicationCommands(d.bot.State.User.ID, d.guildID)
if err != nil {
d.app.err.Printf("Discord: Failed to get commands: %v", err)
return
}
for _, cmd := range existingCommands {
if err := d.bot.ApplicationCommandDelete(d.bot.State.User.ID, "", cmd.ID); err != nil {
d.app.err.Printf("Failed to deregister command: %v", err)
}
}
}
func (d *DiscordDaemon) commandHandler(s *dg.Session, i *dg.InteractionCreate) {
if h, ok := d.commandHandlers[i.ApplicationCommandData().Name]; ok {
if i.GuildID != "" && d.channelName != "" {
if d.channelID == "" {
channel, err := s.Channel(i.ChannelID)
if err != nil {
d.app.err.Printf("Discord: Couldn't get channel, will monitor all: %v", err)
d.channelName = ""
}
if channel.Name == d.channelName {
d.channelID = channel.ID
}
}
if d.channelID != i.ChannelID {
d.app.debug.Printf("Discord: Ignoring message as not in specified channel")
return
}
}
if i.Interaction.Member.User.ID == s.State.User.ID {
return
}
lang := d.app.storage.lang.chosenTelegramLang
if user, ok := d.users[i.Interaction.Member.User.ID]; ok {
if _, ok := d.app.storage.lang.Telegram[user.Lang]; ok {
lang = user.Lang
}
}
h(s, i, lang)
}
}
// cmd* methods handle slash-commands, msg* methods handle ! commands.
func (d *DiscordDaemon) cmdStart(s *dg.Session, i *dg.InteractionCreate, lang string) {
channel, err := s.UserChannelCreate(i.Interaction.Member.User.ID)
if err != nil {
d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", i.Interaction.Member.User.Username, err)
return
}
user := d.MustGetUser(channel.ID, i.Interaction.Member.User.ID, i.Interaction.Member.User.Discriminator, i.Interaction.Member.User.Username)
d.users[i.Interaction.Member.User.ID] = user
content := d.app.storage.lang.Telegram[lang].Strings.get("discordStartMessage") + "\n"
content += d.app.storage.lang.Telegram[lang].Strings.template("languageMessageDiscord", tmpl{"command": "/lang"})
err = s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
// Type: dg.InteractionResponseChannelMessageWithSource,
Type: dg.InteractionResponseChannelMessageWithSource,
Data: &dg.InteractionResponseData{
Content: content,
Flags: 64, // Ephemeral
},
})
if err != nil {
d.app.err.Printf("Discord: Failed to send reply: %v", err)
return
}
}
func (d *DiscordDaemon) cmdPIN(s *dg.Session, i *dg.InteractionCreate, lang string) {
pin := i.ApplicationCommandData().Options[0].StringValue()
tokenIndex := -1
for i, token := range d.tokens {
if pin == token {
tokenIndex = i
break
}
}
if tokenIndex == -1 {
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
// Type: dg.InteractionResponseChannelMessageWithSource,
Type: dg.InteractionResponseChannelMessageWithSource,
Data: &dg.InteractionResponseData{
Content: d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"),
Flags: 64, // Ephemeral
},
})
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", i.Interaction.Member.User.Username, err)
}
return
}
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
// Type: dg.InteractionResponseChannelMessageWithSource,
Type: dg.InteractionResponseChannelMessageWithSource,
Data: &dg.InteractionResponseData{
Content: d.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"),
Flags: 64, // Ephemeral
},
})
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", i.Interaction.Member.User.Username, err)
}
d.verifiedTokens[pin] = d.users[i.Interaction.Member.User.ID]
d.tokens[len(d.tokens)-1], d.tokens[tokenIndex] = d.tokens[tokenIndex], d.tokens[len(d.tokens)-1]
d.tokens = d.tokens[:len(d.tokens)-1]
}
func (d *DiscordDaemon) cmdLang(s *dg.Session, i *dg.InteractionCreate, lang string) {
code := i.ApplicationCommandData().Options[0].StringValue()
if _, ok := d.app.storage.lang.Telegram[code]; ok {
var user DiscordUser
for jfID, u := range d.app.storage.discord {
if u.ID == i.Interaction.Member.User.ID {
u.Lang = code
lang = code
d.app.storage.discord[jfID] = u
if err := d.app.storage.storeDiscordUsers(); err != nil {
d.app.err.Printf("Failed to store Discord users: %v", err)
}
user = u
break
}
}
d.users[i.Interaction.Member.User.ID] = user
err := s.InteractionRespond(i.Interaction, &dg.InteractionResponse{
// Type: dg.InteractionResponseChannelMessageWithSource,
Type: dg.InteractionResponseChannelMessageWithSource,
Data: &dg.InteractionResponseData{
Content: d.app.storage.lang.Telegram[lang].Strings.template("languageSet", tmpl{"language": d.app.storage.lang.Telegram[lang].Meta.Name}),
Flags: 64, // Ephemeral
},
})
if err != nil {
d.app.err.Printf("Discord: Failed to send reply: %v", err)
return
}
}
}
func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
if m.GuildID != "" && d.channelName != "" {
if d.channelID == "" {
@@ -255,16 +529,16 @@ func (d *DiscordDaemon) messageHandler(s *dg.Session, m *dg.MessageCreate) {
}
}
switch msg := sects[0]; msg {
case d.app.config.Section("discord").Key("start_command").MustString("!start"):
d.commandStart(s, m, lang)
case "!" + d.app.config.Section("discord").Key("start_command").MustString("start"):
d.msgStart(s, m, lang)
case "!lang":
d.commandLang(s, m, sects, lang)
d.msgLang(s, m, sects, lang)
default:
d.commandPIN(s, m, sects, lang)
d.msgPIN(s, m, sects, lang)
}
}
func (d *DiscordDaemon) commandStart(s *dg.Session, m *dg.MessageCreate, lang string) {
func (d *DiscordDaemon) msgStart(s *dg.Session, m *dg.MessageCreate, lang string) {
channel, err := s.UserChannelCreate(m.Author.ID)
if err != nil {
d.app.err.Printf("Discord: Failed to create private channel with \"%s\": %v", m.Author.Username, err)
@@ -272,6 +546,13 @@ func (d *DiscordDaemon) commandStart(s *dg.Session, m *dg.MessageCreate, lang st
}
user := d.MustGetUser(channel.ID, m.Author.ID, m.Author.Discriminator, m.Author.Username)
d.users[m.Author.ID] = user
_, err = d.bot.ChannelMessageSendReply(m.ChannelID, d.app.storage.lang.Telegram[lang].Strings.get("discordDMs"), m.Reference())
if err != nil {
d.app.err.Printf("Discord: Failed to send reply to \"%s\": %v", m.Author.Username, err)
return
}
content := d.app.storage.lang.Telegram[lang].Strings.get("startMessage") + "\n"
content += d.app.storage.lang.Telegram[lang].Strings.template("languageMessage", tmpl{"command": "!lang"})
_, err = s.ChannelMessageSend(channel.ID, content)
@@ -281,7 +562,7 @@ func (d *DiscordDaemon) commandStart(s *dg.Session, m *dg.MessageCreate, lang st
}
}
func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
func (d *DiscordDaemon) msgLang(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
if len(sects) == 1 {
list := "!lang <lang>\n"
for code := range d.app.storage.lang.Telegram {
@@ -299,13 +580,14 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
}
if _, ok := d.app.storage.lang.Telegram[sects[1]]; ok {
var user DiscordUser
for jfID, user := range d.app.storage.discord {
if user.ID == m.Author.ID {
user.Lang = sects[1]
d.app.storage.discord[jfID] = user
for jfID, u := range d.app.storage.discord {
if u.ID == m.Author.ID {
u.Lang = sects[1]
d.app.storage.discord[jfID] = u
if err := d.app.storage.storeDiscordUsers(); err != nil {
d.app.err.Printf("Failed to store Discord users: %v", err)
}
user = u
break
}
}
@@ -313,7 +595,7 @@ func (d *DiscordDaemon) commandLang(s *dg.Session, m *dg.MessageCreate, sects []
}
}
func (d *DiscordDaemon) commandPIN(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
func (d *DiscordDaemon) msgPIN(s *dg.Session, m *dg.MessageCreate, sects []string, lang string) {
if _, ok := d.users[m.Author.ID]; ok {
channel, err := s.Channel(m.ChannelID)
if err != nil {

231
email.go
View File

@@ -10,19 +10,17 @@ import (
"html/template"
"io"
"io/fs"
"net/smtp"
"os"
"strconv"
"strings"
"sync"
textTemplate "text/template"
"time"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/itchyny/timefmt-go"
jEmail "github.com/jordan-wright/email"
"github.com/mailgun/mailgun-go/v4"
sMail "github.com/xhit/go-simple-mail/v2"
)
var renderer = html.NewRenderer(html.RendererOptions{Flags: html.Smartypants})
@@ -32,70 +30,6 @@ type EmailClient interface {
Send(fromName, fromAddr string, message *Message, address ...string) error
}
type DummyClient struct{}
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
}
// Mailgun client implements EmailClient.
type Mailgun struct {
client *mailgun.MailgunImpl
}
func (mg *Mailgun) Send(fromName, fromAddr string, email *Message, address ...string) error {
message := mg.client.NewMessage(
fmt.Sprintf("%s <%s>", fromName, fromAddr),
email.Subject,
email.Text,
)
for _, a := range address {
// Adding variable tells mailgun to do a batch send, so users don't see other recipients.
message.AddRecipientAndVariables(a, map[string]interface{}{"unique_id": a})
}
message.SetHtml(email.HTML)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, _, err := mg.client.Send(ctx, message)
return err
}
// SMTP supports SSL/TLS and STARTTLS; implements EmailClient.
type SMTP struct {
sslTLS bool
server string
port int
auth smtp.Auth
tlsConfig *tls.Config
}
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
var err error
for _, addr := range address {
wg.Add(1)
go func(addr string) {
defer wg.Done()
e := jEmail.NewEmail()
e.Subject = email.Subject
e.From = from
e.Text = []byte(email.Text)
e.HTML = []byte(email.HTML)
e.To = []string{addr}
if sm.sslTLS {
err = e.SendWithTLS(server, sm.auth, sm.tlsConfig)
} else {
err = e.SendWithStartTLS(server, sm.auth, sm.tlsConfig)
}
}(addr)
}
wg.Wait()
return err
}
// Emailer contains the email sender, translations, and methods to construct messages.
type Emailer struct {
fromAddr, fromName string
@@ -145,13 +79,12 @@ func NewEmailer(app *appContext) *Emailer {
if app.config.Section("smtp").Key("encryption").String() == "ssl_tls" {
sslTLS = true
}
username := ""
if u := app.config.Section("smtp").Key("username").MustString(""); u != "" {
username = u
} else {
username := app.config.Section("smtp").Key("username").MustString("")
password := app.config.Section("smtp").Key("password").String()
if username == "" && password != "" {
username = emailer.fromAddr
}
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, app.config.Section("smtp").Key("password").String(), sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""))
err := emailer.NewSMTP(app.config.Section("smtp").Key("server").String(), app.config.Section("smtp").Key("port").MustInt(465), username, password, sslTLS, app.config.Section("smtp").Key("ssl_cert").MustString(""), app.config.Section("smtp").Key("hello_hostname").String(), app.config.Section("smtp").Key("cert_validation").MustBool(true))
if err != nil {
app.err.Printf("Error while initiating SMTP mailer: %v", err)
}
@@ -163,34 +96,45 @@ func NewEmailer(app *appContext) *Emailer {
return emailer
}
// NewMailgun returns a Mailgun emailClient.
func (emailer *Emailer) NewMailgun(url, key string) {
sender := &Mailgun{
client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key),
}
// Mailgun client takes the base url, so we need to trim off the end (e.g 'v3/messages')
if strings.Contains(url, "messages") {
url = url[0:strings.LastIndex(url, "/")]
url = url[0:strings.LastIndex(url, "/")]
}
sender.client.SetAPIBase(url)
emailer.sender = sender
// DummyClient just logs the email to the console for debugging purposes. It can be used by settings [email]/method to "dummy".
type DummyClient struct{}
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
}
// SMTP supports SSL/TLS and STARTTLS; implements EmailClient.
type SMTP struct {
Client *sMail.SMTPServer
}
// NewSMTP returns an SMTP emailClient.
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string) (err error) {
func (emailer *Emailer) NewSMTP(server string, port int, username, password string, sslTLS bool, certPath string, helloHostname string, validateCertificate bool) (err error) {
sender := &SMTP{}
sender.Client = sMail.NewSMTPClient()
if sslTLS {
sender.Client.Encryption = sMail.EncryptionSSLTLS
} else {
sender.Client.Encryption = sMail.EncryptionSTARTTLS
}
if username != "" || password != "" {
sender.Client.Authentication = sMail.AuthLogin
sender.Client.Username = username
sender.Client.Password = password
}
sender.Client.Helo = helloHostname
sender.Client.ConnectTimeout, sender.Client.SendTimeout = 15*time.Second, 15*time.Second
sender.Client.Host = server
sender.Client.Port = port
sender.Client.KeepAlive = false
// x509.SystemCertPool is unavailable on windows
if PLATFORM == "windows" {
emailer.sender = &SMTP{
auth: smtp.PlainAuth("", username, password, server),
server: server,
port: port,
sslTLS: sslTLS,
tlsConfig: &tls.Config{
InsecureSkipVerify: false,
ServerName: server,
},
sender.Client.TLSConfig = &tls.Config{
InsecureSkipVerify: !validateCertificate,
ServerName: server,
}
emailer.sender = sender
return
}
rootCAs, err := x509.SystemCertPool()
@@ -204,20 +148,74 @@ func (emailer *Emailer) NewSMTP(server string, port int, username, password stri
err = errors.New("Failed to append cert to pool")
}
}
emailer.sender = &SMTP{
auth: smtp.PlainAuth("", username, password, server),
server: server,
port: port,
sslTLS: sslTLS,
tlsConfig: &tls.Config{
InsecureSkipVerify: false,
ServerName: server,
RootCAs: rootCAs,
},
sender.Client.TLSConfig = &tls.Config{
InsecureSkipVerify: !validateCertificate,
ServerName: server,
RootCAs: rootCAs,
}
emailer.sender = sender
return
}
func (sm *SMTP) Send(fromName, fromAddr string, email *Message, address ...string) error {
from := fmt.Sprintf("%s <%s>", fromName, fromAddr)
var cli *sMail.SMTPClient
var err error
cli, err = sm.Client.Connect()
if err != nil {
return err
}
defer cli.Close()
e := sMail.NewMSG()
e.SetFrom(from)
e.SetSubject(email.Subject)
e.AddTo(address...)
e.SetBody(sMail.TextPlain, email.Text)
if email.HTML != "" {
e.AddAlternative(sMail.TextHTML, email.HTML)
}
err = e.Send(cli)
return err
}
// Mailgun client implements EmailClient.
type Mailgun struct {
client *mailgun.MailgunImpl
}
// NewMailgun returns a Mailgun emailClient.
func (emailer *Emailer) NewMailgun(url, key string) {
sender := &Mailgun{
client: mailgun.NewMailgun(strings.Split(emailer.fromAddr, "@")[1], key),
}
// Mailgun client takes the base url, so we need to trim off the end (e.g 'v3/messages')
if strings.Contains(url, "messages") {
url = url[0:strings.LastIndex(url, "/")]
}
if strings.Contains(url, "v3") {
url = url[0:strings.LastIndex(url, "/")]
}
sender.client.SetAPIBase(url)
emailer.sender = sender
}
func (mg *Mailgun) Send(fromName, fromAddr string, email *Message, address ...string) error {
message := mg.client.NewMessage(
fmt.Sprintf("%s <%s>", fromName, fromAddr),
email.Subject,
email.Text,
)
for _, a := range address {
// Adding variable tells mailgun to do a batch send, so users don't see other recipients.
message.AddRecipientAndVariables(a, map[string]interface{}{"unique_id": a})
}
message.SetHtml(email.HTML)
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
_, _, err := mg.client.Send(ctx, message)
return err
}
type templ interface {
Execute(wr io.Writer, data interface{}) error
}
@@ -306,6 +304,9 @@ func (emailer *Emailer) confirmationValues(code, username, key string, app *appC
} else {
message := app.config.Section("messages").Key("message").String()
inviteLink := app.config.Section("invite_emails").Key("url_base").String()
if !strings.HasSuffix(inviteLink, "/invite") {
inviteLink += "/invite"
}
inviteLink = fmt.Sprintf("%s/%s?key=%s", inviteLink, code, key)
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": username})
template["confirmationURL"] = inviteLink
@@ -513,6 +514,18 @@ func (emailer *Emailer) constructCreated(code, username, address string, invite
return email, nil
}
// GenResetLink generates and returns a password reset link.
func (app *appContext) GenResetLink(pin string) (string, error) {
url := app.config.Section("password_resets").Key("url_base").String()
var pinLink string
if url == "" {
return pinLink, fmt.Errorf("disabled as no URL Base provided. Set in Settings > Password Resets.")
}
// Strip /invite from end of this URL, ik it's ugly.
pinLink = fmt.Sprintf("%s/reset?pin=%s", url, pin)
return pinLink, nil
}
func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bool) map[string]interface{} {
d, t, expiresIn := emailer.formatExpiry(pwr.Expiry, true, app.datePattern, app.timePattern)
message := app.config.Section("messages").Key("message").String()
@@ -543,17 +556,16 @@ func (emailer *Emailer) resetValues(pwr PasswordReset, app *appContext, noSub bo
} else {
template["helloUser"] = emailer.lang.Strings.template("helloUser", tmpl{"username": pwr.Username})
template["codeExpiry"] = emailer.lang.PasswordReset.template("codeExpiry", tmpl{"date": d, "time": t, "expiresInMinutes": expiresIn})
url := app.config.Section("password_resets").Key("url_base").String()
if linkResetEnabled {
if url != "" {
pinLink, err := app.GenResetLink(pwr.Pin)
if err == nil {
// Strip /invite form end of this URL, ik its ugly.
template["link_reset"] = true
pinLink := fmt.Sprintf("%s/reset?pin=%s", url, pwr.Pin)
template["pin"] = pinLink
// Only used in html email.
template["pin_code"] = pwr.Pin
} else {
app.info.Println("Password Reset link disabled as no URL Base provided. Set in Settings > Password Resets.")
app.info.Println("Couldn't generate PWR link: %v", err)
template["pin"] = pwr.Pin
}
} else {
@@ -602,7 +614,6 @@ func (emailer *Emailer) deletedValues(reason string, app *appContext, noSub bool
template["reason"] = reason
template["message"] = app.config.Section("messages").Key("message").String()
}
fmt.Println("TTTT", template)
return template
}
@@ -852,7 +863,7 @@ func (app *appContext) sendByID(email *Message, ID ...string) error {
func (app *appContext) getAddressOrName(jfID string) string {
if dcChat, ok := app.storage.discord[jfID]; ok && dcChat.Contact && discordEnabled {
return dcChat.Username + "#" + dcChat.Discriminator
return RenderDiscordUsername(dcChat)
}
if tgChat, ok := app.storage.telegram[jfID]; ok && tgChat.Contact && telegramEnabled {
return "@" + tgChat.Username

36
exit.go
View File

@@ -5,13 +5,14 @@ import (
"html/template"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"time"
"github.com/pkg/browser"
"github.com/robert-nix/ansihtml"
)
// https://gist.github.com/swdunlop/9629168
@@ -43,6 +44,21 @@ func identifyPanic() string {
return fmt.Sprintf("pc:%x", pc)
}
// OpenFile attempts to open a given file in the appropriate GUI application.
func OpenFile(fpath string) (err error) {
switch PLATFORM {
case "linux":
err = exec.Command("xdg-open", fpath).Start()
case "windows":
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", fpath).Start()
case "darwin":
err = exec.Command("open", fpath).Start()
default:
err = fmt.Errorf("unknown os")
}
return
}
// Exit dumps the last 100 lines of output to a crash file in /tmp (or equivalent), and generates a prettier HTML file containing it that is opened in the browser if possible.
func Exit(err interface{}) {
tmpl, err2 := template.ParseFS(localFS, "html/crash.html", "html/header.html")
@@ -50,6 +66,10 @@ func Exit(err interface{}) {
log.Fatalf("Failed to load template: %v", err)
}
logCache := lineCache.String()
if err != nil {
fmt.Println(err)
logCache += "\n" + fmt.Sprint(err)
}
logCache += "\n" + string(debug.Stack())
sanitized := sanitizeLog(logCache)
data := map[string]interface{}{
@@ -59,12 +79,19 @@ func Exit(err interface{}) {
if err != nil {
data["Err"] = fmt.Sprintf("%s %v", identifyPanic(), err)
}
fpath := filepath.Join(temp, "jfa-go-crash-"+time.Now().Local().Format("2006-01-02T15:04:05"))
// Use dashes for time rather than colons for Windows
fpath := filepath.Join(temp, "jfa-go-crash-"+time.Now().Local().Format("2006-01-02T15-04-05"))
err2 = os.WriteFile(fpath+".txt", []byte(logCache), 0666)
if err2 != nil {
log.Fatalf("Failed to write crash dump file: %v", err2)
}
log.Printf("\n------\nA crash report has been saved to \"%s\".\n------", fpath+".txt")
// Render ANSI colors to HTML
data["Log"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["Log"].(string)))))
data["SanitizedLog"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["SanitizedLog"].(string)))))
data["Err"] = template.HTML(string(ansihtml.ConvertToHTML([]byte(data["Err"].(string)))))
f, err2 := os.OpenFile(fpath+".html", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err2 != nil {
log.Fatalf("Failed to open crash dump file: %v", err2)
@@ -74,7 +101,10 @@ func Exit(err interface{}) {
if err2 != nil {
log.Fatalf("Failed to execute template: %v", err2)
}
browser.OpenFile(fpath + ".html")
if err := OpenFile(fpath + ".html"); err != nil {
log.Printf("Failed to open browser, trying text file...")
OpenFile(fpath + ".txt")
}
if TRAY {
QuitTray()
} else {

147
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/hrfee/jfa-go
go 1.16
go 1.20
replace github.com/hrfee/jfa-go/docs => ./docs
@@ -12,63 +12,104 @@ replace github.com/hrfee/jfa-go/logger => ./logger
replace github.com/hrfee/jfa-go/linecache => ./linecache
replace github.com/hrfee/jfa-go/api => ./api
require (
github.com/bwmarrin/discordgo v0.23.2
github.com/elazarl/go-bindata-assetfs v1.0.0 // indirect
github.com/bwmarrin/discordgo v0.27.1
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
github.com/fatih/color v1.12.0
github.com/fsnotify/fsnotify v1.4.9
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f // indirect
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730 // indirect
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 // indirect
github.com/getlantern/systray v1.1.0
github.com/gin-contrib/pprof v1.3.0
github.com/fatih/color v1.15.0
github.com/fsnotify/fsnotify v1.6.0
github.com/getlantern/systray v1.2.2
github.com/gin-contrib/pprof v1.4.0
github.com/gin-contrib/static v0.0.1
github.com/gin-gonic/gin v1.7.4
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/validator/v10 v10.9.0 // indirect
github.com/gin-gonic/gin v1.9.1
github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/golang/protobuf v1.5.2 // indirect
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7
github.com/google/uuid v1.3.0 // indirect
github.com/hrfee/jfa-go/common v0.0.0-20210816220108-eb3489b34f9e
github.com/hrfee/jfa-go/docs v0.0.0-20210816220108-eb3489b34f9e
github.com/hrfee/jfa-go/linecache v0.0.0-20210816220108-eb3489b34f9e
github.com/hrfee/jfa-go/logger v0.0.0-20210816220108-eb3489b34f9e
github.com/hrfee/jfa-go/ombi v0.0.0-20210816220108-eb3489b34f9e
github.com/hrfee/mediabrowser v0.3.5
github.com/itchyny/timefmt-go v0.1.3
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible
github.com/json-iterator/go v1.1.11 // indirect
github.com/lib/pq v1.10.2 // indirect
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a
github.com/hrfee/jfa-go/common v0.0.0-20230421170108-d800b97f69b6
github.com/hrfee/jfa-go/docs v0.0.0-20230421170108-d800b97f69b6
github.com/hrfee/jfa-go/linecache v0.0.0-20230421170108-d800b97f69b6
github.com/hrfee/jfa-go/logger v0.0.0-20230421170108-d800b97f69b6
github.com/hrfee/jfa-go/ombi v0.0.0-20230421170108-d800b97f69b6
github.com/hrfee/mediabrowser v0.3.8
github.com/itchyny/timefmt-go v0.1.5
github.com/lithammer/shortuuid/v3 v3.0.7
github.com/mailgun/mailgun-go/v4 v4.5.2
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.7 // indirect
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2
github.com/pkg/errors v0.9.1 // indirect
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
github.com/smartystreets/goconvey v1.6.4 // indirect
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
github.com/swaggo/gin-swagger v1.3.1
github.com/swaggo/swag v1.7.1 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tidwall/gjson v1.8.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/sjson v1.1.7 // indirect
github.com/ugorji/go v1.2.6 // indirect
github.com/mailgun/mailgun-go/v4 v4.9.0
github.com/robert-nix/ansihtml v1.0.1
github.com/steambap/captcha v1.4.1
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.0
github.com/writeas/go-strip-markdown v2.0.1+incompatible
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e // indirect
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d // indirect
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/tools v0.1.5 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/go-playground/validator.v9 v9.29.1 // indirect
gopkg.in/ini.v1 v1.62.0
maunium.net/go/maulogger/v2 v2.3.0 // indirect
maunium.net/go/mautrix v0.9.19
github.com/xhit/go-simple-mail/v2 v2.13.0
gopkg.in/ini.v1 v1.67.0
maunium.net/go/mautrix v0.15.2
)
require (
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 // indirect
github.com/getlantern/errors v1.0.3 // indirect
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 // indirect
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect
github.com/getlantern/ops v0.0.0-20230519221840-1283e026181c // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
github.com/go-openapi/spec v0.20.9 // indirect
github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.14.1 // indirect
github.com/go-stack/stack v1.8.1 // indirect
github.com/go-test/deep v1.1.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.5 // indirect
github.com/leodido/go-urn v1.2.4 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rs/zerolog v1.29.1 // indirect
github.com/swaggo/swag v1.16.1 // indirect
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
github.com/tidwall/gjson v1.14.4 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.11 // indirect
go.opentelemetry.io/otel v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/otel/trace v1.16.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.24.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/crypto v0.9.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/image v0.7.0 // indirect
golang.org/x/net v0.10.0 // indirect
golang.org/x/sys v0.8.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/tools v0.9.3 // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
maunium.net/go/maulogger/v2 v2.4.1 // indirect
)

473
go.sum
View File

@@ -1,34 +1,27 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA=
github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
github.com/btcsuite/btcutil v1.0.2 h1:9iZ1Terx9fMIOtq1VrwdqfsATL9MC2l8ZrUY6YZ2uts=
github.com/btcsuite/btcutil v1.0.2/go.mod h1:j9HUFwoQRsZL3V4n+qG+CUnEGHOarIxfC3Le2Yhbcts=
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd/go.mod h1:HHNXQzUsZCxOoE+CPiyCTO6x34Zs86zZUiwtpXoGdtg=
github.com/btcsuite/goleveldb v0.0.0-20160330041536-7834afc9e8cd/go.mod h1:F+uVaaLLH7j4eDXPRvw78tMflu7Ie2bzYOH4Y8rRKBY=
github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/bwmarrin/discordgo v0.23.2 h1:BzrtTktixGHIu9Tt7dEE6diysEF9HWnXeHuoJEt2fH4=
github.com/bwmarrin/discordgo v0.23.2/go.mod h1:c1WtWUGN6nREDmzIpyTp/iD3VYt4Fpx+bVyfBG7JE+M=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY=
github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a h1:M88ob4TyDnEqNuL3PgsE/p3bDujfspnulR+0dQWNYZs=
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a/go.mod h1:buzQsO8HHkZX2Q45fdfGH1xejPjuDQaXH8btcYMFzPM=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
@@ -37,313 +30,314 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201 h1:oEZYEpZo28Wdx+5FZo4aU7JFXu0WG/4wJWese5reQSA=
github.com/getlantern/context v0.0.0-20220418194847-3d5e7a086201/go.mod h1:Y9WZUHEb+mpra02CbQ/QczLUe6f0Dezxaw5DCJlJQGo=
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/errors v1.0.1 h1:XukU2whlh7OdpxnkXhNH9VTLVz0EVPGKDV5K0oWhvzw=
github.com/getlantern/errors v1.0.1/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
github.com/getlantern/errors v1.0.3 h1:Ne4Ycj7NI1BtSyAfVeAT/DNoxz7/S2BUc3L2Ht1YSHE=
github.com/getlantern/errors v1.0.3/go.mod h1:m8C7H1qmouvsGpwQqk/6NUpIVMpfzUPn608aBZDYV04=
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f h1:wsVt3P/boVKkPFEZkWxgNgRq/+mD7sWHc17+Vw2PgH8=
github.com/getlantern/golog v0.0.0-20210606115803-bce9f9fe5a5f/go.mod h1:ZyIjgH/1wTCl+B+7yH1DqrWp6MPJqESmwmEQ89ZfhvA=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAXJb+hLVVDGqfvvXGRPV8vp7XOjKAZ0=
github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU=
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc h1:sue+aeVx7JF5v36H1HfvcGFImLpSD5goj8d+MitovDU=
github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc/go.mod h1:D9RWpXy/EFPYxiKUURo2TB8UBosbqkiLhttRrZYtvqM=
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730 h1:oKJVQbWZ2CAJ71jYnm6A3+e6h5bkPJ0okIMwkaYB5HI=
github.com/getlantern/hidden v0.0.0-20201229170000-e66e7f878730/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2yaur9Qk3rHYD414j3Q1rl7+L0AylxrE=
github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A=
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6 h1:QthAQCekS1YOeYWSvoHI6ZatlG4B+GBDLxV/2ZkBsTA=
github.com/getlantern/ops v0.0.0-20200403153110-8476b16edcd6/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/systray v1.1.0 h1:U0wCEqseLi2ok1fE6b88gJklzriavPJixZysZPkZd/Y=
github.com/getlantern/systray v1.1.0/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/getlantern/ops v0.0.0-20220713155959-1315d978fff7/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
github.com/getlantern/ops v0.0.0-20230519221840-1283e026181c h1:qcPAzA1ZDnwx618jAgQmxo6UvJkw2SkM1L4ofncmEhI=
github.com/getlantern/ops v0.0.0-20230519221840-1283e026181c/go.mod h1:g2ueCncOwWenlAr56Fh90FwsACkelqqtFUDLAHg1mng=
github.com/getlantern/systray v1.2.2 h1:dCEHtfmvkJG7HZ8lS/sLklTH4RKUcIsKrAD9sThoEBE=
github.com/getlantern/systray v1.2.2/go.mod h1:pXFOI1wwqwYXEhLPm9ZGjS2u/vVELeIgNMY5HvhHhcE=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/gzip v0.0.1 h1:ezvKOL6jH+jlzdHNE4h9h8q8uMpDQjyl0NN0Jd7jozc=
github.com/gin-contrib/gzip v0.0.1/go.mod h1:fGBJBCdt6qCZuCAOwWuFhBB4OOq9EFqlo5dEaFhhu5w=
github.com/gin-contrib/pprof v1.3.0 h1:G9eK6HnbkSqDZBYbzG4wrjCsA4e+cvYAHUZw6W+W9K0=
github.com/gin-contrib/pprof v1.3.0/go.mod h1:waMjT1H9b179t3CxuG1cV3DHpga6ybizwfBaM5OXaB0=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/pprof v1.4.0 h1:XxiBSf5jWZ5i16lNOPbMTVdgHBdhfGRD5PZ1LWazzvg=
github.com/gin-contrib/pprof v1.4.0/go.mod h1:RrehPJasUVBPK6yTUwOl8/NP6i0vbUgmxtis+Z5KE90=
github.com/gin-contrib/sse v0.0.0-20170109093832-22d885f9ecc7/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e h1:8bZpGwoPxkaivQPrAbWl+7zjjUcbFUnYp7yQcx2r2N0=
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
github.com/gin-gonic/gin v1.3.0/go.mod h1:7cKuhb5qV2ggCFctp2fJQ+ErvciLZrIeoOSOm6mUr7Y=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.6.2/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.0/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.7.4 h1:QmUZXrvJ9qZ3GfWvQ+2wnW/1ePrTEJqPKMYEU3lD/DM=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk=
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
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=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I=
github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.20.3 h1:uH9RQ6vdyPSs2pSy9fL8QPspDF2AMIMPtmK5coSSjtQ=
github.com/go-openapi/spec v0.20.3/go.mod h1:gG4F8wdEDN+YPBMVnzE85Rbhf+Th2DTvA9nFPQ5AYEg=
github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8=
github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg=
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
github.com/go-playground/validator/v10 v10.14.1 h1:9c50NUPC30zyuKprjL3vNZ0m5oG+jU0zvx4AqHGnv4k=
github.com/go-playground/validator/v10 v10.14.1/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
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-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg=
github.com/go-test/deep v1.1.0/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
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/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7 h1:oKYOfNR7Hp6XpZ4JqolL5u642Js5Z0n7psPVl+S5heo=
github.com/gomarkdown/markdown v0.0.0-20210514010506-3b9f47219fe7/go.mod h1:aii0r/K0ZnHv7G0KF7xy1v0A7s2Ljrb5byB7MO5p6TU=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a h1:AWZzzFrqyjYlRloN6edwTLTUbKxf5flLXNuTBDm3Ews=
github.com/gomarkdown/markdown v0.0.0-20230322041520-c84983bdbf2a/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/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/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hrfee/mediabrowser v0.3.5 h1:bOJlI2HLvw7v0c7mcRw5XDRMUHReQzk5z0EJYRyYjpo=
github.com/hrfee/mediabrowser v0.3.5/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/itchyny/timefmt-go v0.1.3 h1:7M3LGVDsqcd0VZH2U+x393obrzZisp7C0uEe921iRkU=
github.com/itchyny/timefmt-go v0.1.3/go.mod h1:0osSSCQSASBJMsIZnhAaF1C2fCBTJZXrnj37mG8/c+A=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hrfee/mediabrowser v0.3.8 h1:y0iBCb6jE3QKcsiCJSYva2fFPHRn4UA+sGRzoPuJ/Dk=
github.com/hrfee/mediabrowser v0.3.8/go.mod h1:PnHZbdxmbv1wCVdAQyM7nwPwpVj9fdKx2EcET7sAk+U=
github.com/itchyny/timefmt-go v0.1.5 h1:G0INE2la8S6ru/ZI5JecgyzbbJNs5lG1RcBqa7Jm6GE=
github.com/itchyny/timefmt-go v0.1.5/go.mod h1:nEP7L+2YmAbT2kZ2HfSs1d8Xtw9LY8D2stDBckWakZ8=
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/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8=
github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
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/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lithammer/shortuuid/v3 v3.0.7 h1:trX0KTHy4Pbwo/6ia8fscyHoGA+mf1jWbPJVuvyJQQ8=
github.com/lithammer/shortuuid/v3 v3.0.7/go.mod h1:vMk8ke37EmiewwolSO1NLW8vP4ZaKlRuDIi8tWWmAts=
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/mailgun/mailgun-go/v4 v4.5.2 h1:6289rnnSn8pKFR8TS5pwWOqFcY/HdWpqhvnQ+J7IVQc=
github.com/mailgun/mailgun-go/v4 v4.5.2/go.mod h1:FJlF9rI5cQT+mrwujtJjPMbIVy3Ebor9bKTVsJ0QU40=
github.com/lxn/walk v0.0.0-20210112085537-c389da54e794/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ=
github.com/lxn/win v0.0.0-20210218163916-a377121e959e/go.mod h1:KxxjdtRkfNoYDCUP5ryK7XJJNTnpC8atvtmTheChOtk=
github.com/mailgun/mailgun-go/v4 v4.9.0 h1:wRbxvVQ5QObFewLxc1uVvipA16D8gxeiO+cBOca51Iw=
github.com/mailgun/mailgun-go/v4 v4.9.0/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=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA=
github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32 h1:K3WnH8Ka32vWygzmjKEhz1zAVqckNoWDqX3azMxuiSA=
github.com/pkg/browser v0.0.0-20210606212950-a7b7a6107d32/go.mod h1:yvwcBfzEX4m+eTgxPBbNYytaWFv4PSQzBaeYjxp8Iik=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 h1:acNfDZXmm28D2Yg/c3ALnZStzNaZMSagpbr96vY6Zjc=
github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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/robert-nix/ansihtml v1.0.1 h1:VTiyQ6/+AxSJoSSLsMecnkh8i0ZqOEdiRl/odOc64fc=
github.com/robert-nix/ansihtml v1.0.1/go.mod h1:CJwclxYaTPc2RfcxtanEACsYuTksh4yDXcNeHHKZINE=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc=
github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
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/steambap/captcha v1.4.1 h1:OmMdxLCWCqJvsFaFYwRpvMckIuvI6s8s1LsBrBw97P0=
github.com/steambap/captcha v1.4.1/go.mod h1:oC9T7IfEgnrhzjDz5Djf1H7GPffCzRMbsQfFkJmhlnk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY=
github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM=
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI=
github.com/swaggo/gin-swagger v1.3.0 h1:eOmp7r57oUgZPw2dJOjcGNMse9cvXcI4tTqBcnZtPsI=
github.com/swaggo/gin-swagger v1.3.0/go.mod h1:oy1BRA6WvgtCp848lhxce7BnWH4C8Bxa0m5SkWx+cS0=
github.com/swaggo/gin-swagger v1.3.1 h1:mO9MU8O99WX+RM3jekzOV54g9Fo+Nbkk7rgrN1u9irM=
github.com/swaggo/gin-swagger v1.3.1/go.mod h1:Z6NtRBK2PRig0EUmy1Xu75CnCEs6vGYu9QZd/QWRYKU=
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
github.com/swaggo/swag v1.5.1/go.mod h1:1Bl9F/ZBpVWh22nY0zmYyASPO1lI/zIwRDrpZU+tv8Y=
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
github.com/swaggo/swag v1.7.1 h1:gY9ZakXlNWg/i/v5bQBic7VMZ4teq4m89lpiao74p/s=
github.com/swaggo/swag v1.7.1/go.mod h1:gAiHxNTb9cIpNmA/VEGUP+CyZMCP/EW7mdtc8Bny+p8=
github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg=
github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto=
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
github.com/tidwall/gjson v1.6.8 h1:CTmXMClGYPAmln7652e69B7OLXfTi5ABcPPwjIWUv7w=
github.com/tidwall/gjson v1.6.8/go.mod h1:zeFuBCIqD4sN/gmqBzZ4j7Jd6UcA2Fc56x7QFsv+8fI=
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/gjson v1.8.1 h1:8j5EE9Hrh3l9Od1OIEDAb7IpezNA20UdRngNAj5N0WU=
github.com/tidwall/gjson v1.8.1/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE=
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU=
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM=
github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.1.5 h1:wsUceI/XDyZk3J1FUvuuYlK62zJv2HO2Pzb8A5EWdUE=
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
github.com/tidwall/sjson v1.1.7 h1:sgVPwu/yygHJ2m1pJDLgGM/h+1F5odx5Q9ljG3imRm8=
github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs=
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208 h1:PM5hJF7HVfNWmCjMdEfbuOBNXSVF2cMFGgQTPdKCbwM=
github.com/toorop/go-dkim v0.0.0-20201103131630-e1cd1a0a5208/go.mod h1:BzWtXXrXzZUvMacR0oF/fbDDgUPO8L36tDMmRAf14ns=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
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=
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
github.com/ugorji/go v1.2.6/go.mod h1:anCg0y61KIhDlPZmnH+so+RQbysYVyDko0IMgJv0Nn0=
github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M=
github.com/ugorji/go/codec v0.0.0-20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.5-pre/go.mod h1:tULtS6Gy1AE1yCENaw4Vb//HLH5njI2tfCQDUqRd8fI=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ=
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/writeas/go-strip-markdown v2.0.1+incompatible h1:IIqxTM5Jr7RzhigcL6FkrCNfXkvbR+Nbu1ls48pXYcw=
github.com/writeas/go-strip-markdown v2.0.1+incompatible/go.mod h1:Rsyu10ZhbEK9pXdk8V6MVnZmTzRG0alMNLMwa0J01fE=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/xhit/go-simple-mail/v2 v2.13.0 h1:OANWU9jHZrVfBkNkvLf8Ww0fexwpQVF/v/5f96fFTLI=
github.com/xhit/go-simple-mail/v2 v2.13.0/go.mod h1:b7P5ygho6SYE+VIqpxA6QkYfv4teeyG4MKqB3utRu98=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
golang.org/dl v0.0.0-20190829154251-82a15e2f2ead/go.mod h1:IUMfjQLJQd4UTqG1Z90tenwKoCX93Gn3MAQJMOSBsDQ=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opentelemetry.io/otel v1.9.0/go.mod h1:np4EoPGzoPs3O67xUVNoPPcmSvsfOxNlNA4F4AC+0Eo=
go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s=
go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4=
go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo=
go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4=
go.opentelemetry.io/otel/trace v1.9.0/go.mod h1:2737Q0MuG8q1uILYm2YYVkAyLtOofiTNGg6VODnOiPo=
go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs=
go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI=
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI=
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60=
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 h1:/UOmuWzQfxxo9UtlXMwuQU8CMgg1eZXqTRwkSQJWKOI=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e h1:VvfwVmMH40bpMeizC9/K7ipM5Qjucuu16RWfneFPyhQ=
golang.org/x/crypto v0.0.0-20210813211128-0a44fdfbc16e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/image v0.7.0 h1:gzS29xtG1J5ybQlv0PuyfE3nmc6R4qB73m6LUUmvFuw=
golang.org/x/image v0.7.0/go.mod h1:nd/q4ef1AKKYl/4kft7g+6UyGbdiqWqTP1ZAbRoV7Rg=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -353,109 +347,96 @@ golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210220033124-5f55cee0dc0d/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d h1:LO7XpTYMwTqxjLcGWPijK3vRXg1aWdlNOVOHRq45d7c=
golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20181228144115-9a3f9b0469bb/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201018230417-eeed37f84f13/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912 h1:uCLL3g5wH2xjxVREVuAbP9JM5PPKjRbXKRa6IBjkzmU=
golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.3 h1:L69ShwSZEyCsLKoAxDKeMvLDZkumEe8gXUZAjab0tX8=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/Knetic/govaluate.v3 v3.0.0/go.mod h1:csKLBORsPbafmSCGTEh3U7Ozmsuq8ZSIlKk1bcqph0E=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/maulogger/v2 v2.2.4 h1:oV2GDeM4fx1uRysdpDC0FcrPg+thFicSd9XzPcYMbVY=
maunium.net/go/maulogger/v2 v2.2.4/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/maulogger/v2 v2.3.0 h1:TMCcO65fLk6+pJXo7sl38tzjzW0KBFgc6JWJMBJp4GE=
maunium.net/go/maulogger/v2 v2.3.0/go.mod h1:TYWy7wKwz/tIXTpsx8G3mZseIRiC5DoMxSZazOHy68A=
maunium.net/go/mautrix v0.9.15 h1:9EJMALSxvy5LkQh5zjaMhbhm/grzVHv3xoA/2pfxpek=
maunium.net/go/mautrix v0.9.15/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
maunium.net/go/mautrix v0.9.19 h1:8ZoDuijJOKxgEOMDoBN2B6at0Ba7EJpsqWA/5jV7ELw=
maunium.net/go/mautrix v0.9.19/go.mod h1:7IzKfWvpQtN+W2Lzxc0rLvIxFM3ryKX6Ys3S/ZoWbg8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/maulogger/v2 v2.4.1 h1:N7zSdd0mZkB2m2JtFUsiGTQQAdP0YeFWT7YMc80yAL8=
maunium.net/go/maulogger/v2 v2.4.1/go.mod h1:omPuYwYBILeVQobz8uO3XC8DIRuEb5rXYlQSuqrbCho=
maunium.net/go/mautrix v0.15.2 h1:fUiVajeoOR92uJoSShHbCvh7uG6lDY4ZO4Mvt90LbjU=
maunium.net/go/mautrix v0.15.2/go.mod h1:h4NwfKqE4YxGTLSgn/gawKzXAb2sF4qx8agL6QEFtGg=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

View File

@@ -1,16 +1,18 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>404 - jfa-go</title>
</head>
<body class="section">
<div class="page-container">
<h1 class="heading">Page not found.</h1>
<p class="content">
{{ .contactMessage }}
</p>
<div class="card">
<h1 class="heading">Page not found.</h1>
<p class="content">
{{ .contactMessage }}
</p>
</div>
</div>
</body>
</html>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/bundle.css">
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
<script>
window.URLBase = "{{ .urlBase }}";
window.notificationsEnabled = {{ .notifications }};
@@ -12,113 +12,128 @@
window.ombiEnabled = {{ .ombiEnabled }};
window.usernameEnabled = {{ .username }};
window.langFile = JSON.parse({{ .language }});
window.linkResetEnabled = {{ .linkResetEnabled }};
window.language = "{{ .langName }}";
window.jellyfinLogin = {{ .jellyfinLogin }};
window.jfAdminOnly = {{ .jfAdminOnly }};
window.jfAllowAll = {{ .jfAllowAll }};
</script>
{{ template "header.html" . }}
<title>Admin - jfa-go</title>
{{ template "header.html" . }}
</head>
<body class="max-w-full overflow-x-hidden section">
<div id="modal-login" class="modal">
<form class="modal-content card" id="form-login" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-login" href="">
<span class="heading">{{ .strings.login }}</span>
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="login-user">
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="login-password">
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="login-user">
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="login-password">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.login }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.login }}</span>
</label>
</form>
</div>
<div id="modal-add-user" class="modal">
<form class="modal-content card" id="form-add-user" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-add-user" href="">
<span class="heading">{{ .strings.newUser }} <span class="modal-close">&times;</span></span>
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="add-user-user">
<input type="email" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.emailAddress }}">
<input type="password" class="field input ~neutral !high mb-1" placeholder="{{ .strings.password }}" id="add-user-password">
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="add-user-user">
<input type="email" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.emailAddress }}">
<input type="password" class="field input ~neutral @high mb-4" placeholder="{{ .strings.password }}" id="add-user-password">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
</label>
</form>
</div>
<div id="modal-about" class="modal">
<div class="modal-content content card">
<div class="relative mx-auto my-[10%] w-4/5 lg:w-1/3 content card">
<img src="{{ .urlBase }}/banner.svg" class="banner header" alt="jfa-go banner">
<span class="heading"><span class="modal-close">&times;</span></span>
<p><i class="icon ri-github-fill"></i><a href="https://github.com/hrfee/jfa-go">jfa-go</a></p>
<p>{{ .strings.version }} <span class="code monospace">{{ .version }}</span></p>
<p>{{ .strings.commitNoun }} <span class="code monospace">{{ .commit }}</span></p>
<div class="dropdown" tabindex="0">
<span class="button ~info dropdown-button">
<i class="ri-hand-heart-line mr-half"></i>
{{ .strings.donate }}
<span class="ml-1 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral !low">
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">GitHub</a>
<a href="https://ko-fi.com/hrfee" target="_blank" class="button input ~neutral field mb-half lang-link">Ko-fi</a>
<p>{{ .strings.version }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .version }}</span></p>
<p>{{ .strings.commitNoun }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .commit }}</span></p>
<div class="row col flex">
<a class="button ~neutral mr-2 mt-4 mb-4 lang-link" href="https://github.com/hrfee/jfa-go"><i class="ri-github-line mr-2"></i>github</a>
<a class="button ~urge mt-4 mb-4 mr-2 lang-link" href="https://wiki.jfa-go.com">wiki/docs</a>
<a class="button ~positive mt-4 mb-4 mr-2 lang-link" href="https://weblate.jfa-go.com">translation</a>
<div class="dropdown mr-2" tabindex="0">
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~info mt-4 mb-4 dropdown-button lang-link">
<i class="ri-hand-heart-line mr-2"></i>
donate
<span class="ml-2 chev"></span>
</a>
<div class="dropdown-display">
<div class="card ~neutral @low">
<a href="https://github.com/sponsors/hrfee" target="_blank" class="button ~neutral mb-2 w-100 lang-link">GitHub</a>
<a href="https://ko-fi.com/hrfee" target="_blank" class="button ~neutral mb-2 w-100 lang-link">Ko-fi</a>
</div>
</div>
</div>
<a class="button ~urge mt-4 mb-4 @low discord lang-link" href="https://discord.com/invite/MrtvuQmyhP" target="_blank"><i class="ri-discord-line mr-2"></i>discord</a>
</div>
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License.</a></p>
<pre class="monospace">{{ .license }}</pre>
<pre class="font-mono bg-inherit">{{ .license }}</pre>
</div>
</div>
<div id="modal-logs" class="modal">
<div class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content content card">
<span class="heading">{{ .strings.logs }}<span class="modal-close">&times;</span></span>
<pre class="monospace" id="log-area"></pre>
</div>
</div>
<div id="modal-modify-user" class="modal">
<form class="modal-content card" id="form-modify-user" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-modify-user" href="">
<span class="heading"><span id="header-modify-user"></span> <span class="modal-close">&times;</span></span>
<p class="content">{{ .strings.modifySettingsDescription }}</p>
<div class="flex-row mb-1">
<label class="flex-row-group mr-1">
<p class="content my-4">{{ .strings.modifySettingsDescription }}</p>
<div class="flex-row mb-4">
<label class="flex-row-group mr-2">
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-profile" checked>
<span class="button ~neutral !high supra full-width center">{{ .strings.profile }}</span>
<span class="button ~neutral @high supra full-width center">{{ .strings.profile }}</span>
</label>
<label class="flex-row-group ml-1">
<label class="flex-row-group ml-2">
<input type="radio" name="modify-user-source" class="unfocused" id="radio-use-user">
<span class="button ~neutral !normal supra full-width center">{{ .strings.user }}</span>
<span class="button ~neutral @low supra full-width center">{{ .strings.user }}</span>
</label>
</div>
<div class="select ~neutral !normal mb-1">
<div class="select ~neutral @low mb-4">
<select id="modify-user-profiles"></select>
</div>
<div class="select ~neutral !normal mb-1 unfocused">
<div class="select ~neutral @low mb-4 unfocused">
<select id="modify-user-users"></select>
</div>
<label class="switch mb-1">
<label class="switch mb-4">
<input type="checkbox" id="modify-user-homescreen" checked>
<span>{{ .strings.applyHomescreenLayout }}</span>
</label>
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.apply }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.apply }}</span>
</label>
</form>
</div>
<div id="modal-delete-user" class="modal">
<form class="modal-content card" id="form-delete-user" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-delete-user" href="">
<span class="heading"><span id="header-delete-user"></span> <span class="modal-close">&times;</span></span>
<div class="content mt-half">
<label class="switch mb-1">
<div class="content mt-8">
<label class="switch mb-4">
<input type="checkbox" id="delete-user-notify" checked>
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
</label>
<textarea id="textarea-delete-user" class="textarea full-width ~neutral !normal mb-1" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
<textarea id="textarea-delete-user" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
<label>
<input type="submit" class="unfocused">
<span class="button ~critical !normal full-width center supra submit">{{ .strings.delete }}</span>
<span class="button ~critical @low full-width center supra submit">{{ .strings.delete }}</span>
</label>
</div>
</form>
</div>
<div id="modal-extend-expiry" class="modal">
<form class="modal-content card" id="form-extend-expiry" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-extend-expiry" href="">
<span class="heading"><span id="header-extend-expiry"></span> <span class="modal-close">&times;</span></span>
<div class="content mt-half">
<div class="content mt-8">
<div class="row">
<div class="col">
<label class="label supra" for="extend-expiry-months">{{ .strings.inviteMonths }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-months">
<option>0</option>
</select>
@@ -126,7 +141,7 @@
</div>
<div class="col">
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-days">
<option>0</option>
</select>
@@ -136,7 +151,7 @@
<div class="row">
<div class="col">
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-hours">
<option>0</option>
</select>
@@ -144,65 +159,70 @@
</div>
<div class="col">
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-minutes">
<option>0</option>
</select>
</div>
</div>
</div>
<label class="switch mb-4">
<input type="checkbox" id="expiry-extend-enable" checked>
<span>{{ .strings.sendDeleteNotificationEmail }}</span>
</label>
<textarea id="textarea-extend-enable" class="textarea full-width ~neutral @low mb-4" placeholder="{{ .strings.sendDeleteNotificationExample }}"></textarea>
<label>
<input type="submit" class="unfocused">
<span class="button ~critical !normal full-width center supra submit">{{ .strings.submit }}</span>
<span class="button ~critical @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</div>
</form>
</div>
<div id="modal-announce" class="modal">
<form class="modal-content wide card" id="form-announce" href="">
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-announce" href="">
<span class="heading"><span id="header-announce"></span> <span class="modal-close">&times;</span></span>
<div class="row">
<div class="col flex-col content mt-half">
<div class="col card ~neutral @low">
<div id="announce-details">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
<div id="announce-variables">
<span class="button ~urge !normal mb-1 mt-half" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="monospace">{username}</span></span>
<span class="button ~urge @low mb-2 mt-4" id="announce-variables-username" style="margin-left: 0.25rem; margin-right: 0.25rem;"><span class="font-mono bg-inherit">{username}</span></span>
</div>
<label class="label supra" for="announce-subject"> {{ .strings.subject }}</label>
<input type="text" id="announce-subject" class="input ~neutral !normal mb-1 mt-half">
<input type="text" id="announce-subject" class="input ~neutral @low mb-2 mt-4">
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
<textarea id="textarea-announce" class="textarea full-width ~neutral !normal mt-half monospace"></textarea>
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
<textarea id="textarea-announce" class="textarea full-width ~neutral @low mt-4 font-mono"></textarea>
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
</div>
<label class="label unfocused" id="announce-name"><p class="supra">{{ .strings.name }}</p>
<input type="text" class="input ~neutral !normal mb-1 mt-half">
<input type="text" class="input ~neutral @low mb-2 mt-4">
<p class="support">{{ .strings.templateEnterName }}</p>
</label>
<div class="row flex-expand">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal center supra submit">{{ .strings.send }}</span>
<span class="button ~urge @low center supra submit">{{ .strings.send }}</span>
</label>
<span class="button ~info !normal center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
</div>
</div>
<div class="col card ~neutral !low">
<div class="col card ~neutral @low">
<span class="subheading supra">{{ .strings.preview }}</span>
<div class="mt-half" id="announce-preview"></div>
<div class="mt-8" id="announce-preview"></div>
</div>
</div>
</form>
</div>
<div id="modal-customize" class="modal">
<div class="modal-content card">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading">{{ .strings.customizeMessages }} <span class="modal-close">&times;</span></span>
<p class="content">{{ .strings.customizeMessagesDescription }}</p>
<p class="content my-4">{{ .strings.customizeMessagesDescription }}</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{{ .strings.name }}</th>
<th>{{ .strings.reset }}</th>
<th class="table-inline justify-center">{{ .strings.reset }}</th>
<th>{{ .strings.edit }}</th>
</tr>
</thead>
@@ -212,73 +232,83 @@
</div>
</div>
<div id="modal-editor" class="modal">
<form class="modal-content wide card" id="form-editor" href="">
<form class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card" id="form-editor" href="">
<span class="heading"><span id="header-editor"></span> <span class="modal-close">&times;</span></span>
<div class="row">
<div class="col flex-col content mt-half">
<div class="col card ~neutral @low">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
<div id="editor-variables"></div>
<div id="editor-variables" class="mt-4"></div>
<span class="label supra" for="editor-conditionals" id="label-editor-conditionals">{{ .strings.conditionals }}</span>
<div id="editor-conditionals"></div>
<label class="label supra" for="textarea-editor">{{ .strings.message }}</label>
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral !normal mt-half monospace"></textarea>
<p class="support mt-half mb-1">{{ .strings.markdownSupported }}</p>
<textarea id="textarea-editor" class="textarea full-width flex-auto ~neutral @low mt-4 font-mono"></textarea>
<p class="support mt-4 mb-2">{{ .strings.markdownSupported }}</p>
<div class="flex-row">
<label class="full-width ml-half">
<label class="full-width ml-2">
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</div>
</div>
<div class="col card ~neutral !low">
<div class="col card ~neutral @low">
<span class="subheading supra">{{ .strings.preview }}</span>
<div class="mt-half" id="editor-preview"></div>
<div class="mt-8" id="editor-preview"></div>
</div>
</div>
</form>
</div>
<div id="modal-restart" class="modal">
<div class="modal-content card ~critical !low">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~critical @low">
<span class="heading">{{ .strings.settingsRestartRequired }} <span class="modal-close">&times;</span></span>
<p class="content pb-1">{{ .strings.settingsRestartRequiredDescription }}</p>
<div class="fr">
<span class="button ~info !normal mb-half" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
<span class="button ~critical !normal" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
<p class="content my-4">{{ .strings.settingsRestartRequiredDescription }}</p>
<div class="float-right">
<span class="button ~info @low mb-2" id="settings-apply-no-restart">{{ .strings.settingsApplyRestartLater }}</span>
<span class="button ~critical @low" id="settings-apply-restart">{{ .strings.settingsApplyRestartNow }}</span>
</div>
</div>
</div>
<div id="modal-refresh" class="modal">
<div class="modal-content card ~neutral !normal">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low">
<span class="heading">{{ .strings.settingsApplied }}</span>
<p class="content">{{ .strings.settingsRefreshPage }}</p>
</div>
</div>
<div id="modal-ombi-defaults" class="modal">
<form class="modal-content card" id="form-ombi-defaults" href="">
<span class="heading">{{ .strings.ombiUserDefaults }} <span class="modal-close">&times;</span></span>
<p class="content">{{ .strings.ombiUserDefaultsDescription }}</p>
<div class="select ~neutral !normal mb-1">
<div id="modal-send-pwr" class="modal">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low">
<span class="heading">{{ .strings.sendPWR }}</span>
<p class="content my-2" id="send-pwr-note"></p>
<span class="button ~urge @low mt-2" id="send-pwr-link">{{ .strings.copy }}</span>
</div>
</div>
<div id="modal-ombi-profile" class="modal">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-ombi-defaults" href="">
<span class="heading">{{ .strings.ombiProfile }} <span class="modal-close">&times;</span></span>
<p class="content my-4">{{ .strings.ombiUserDefaultsDescription }}</p>
<div class="select ~neutral @low mb-4">
<select></select>
</div>
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</form>
</div>
<div id="modal-user-profiles" class="modal">
<div class="modal-content wide card">
<div class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card">
<span class="heading">{{ .strings.userProfiles }} <span class="modal-close">&times;</span></span>
<p class="support lg">{{ .strings.userProfilesDescription }}</p>
<p class="content my-4">{{ .strings.userProfilesDescription }}</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{{ .strings.name }}</th>
<th>{{ .strings.userProfilesIsDefault }}</th>
{{ if .ombiEnabled }}
<th>Ombi</th>
{{ end }}
<th>{{ .strings.from }}</th>
<th>{{ .strings.userProfilesLibraries }}</th>
<th><span class="button ~neutral !high" id="button-profile-create">{{ .strings.create }}</span></th>
<th><span class="button ~neutral @high" id="button-profile-create">{{ .strings.create }}</span></th>
</tr>
</thead>
<tbody id="table-profiles"></tbody>
@@ -287,146 +317,148 @@
</div>
</div>
<div id="modal-add-profile" class="modal">
<form class="modal-content card" id="form-add-profile" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-add-profile" href="">
<span class="heading">{{ .strings.addProfile }} <span class="modal-close">&times;</span></span>
<p class="content">{{ .strings.addProfileDescription }}</p>
<p class="content my-4">{{ .strings.addProfileDescription }}</p>
<label>
<span class="supra">{{ .strings.addProfileNameOf }} </span>
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.name }}" id="add-profile-name">
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.name }}" id="add-profile-name">
<label>
<span class="supra">{{ .strings.user }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<div class="select ~neutral @low mt-4 mb-2">
<select id="add-profile-user"></select>
</div>
</label>
<label class="switch mb-1">
<label class="switch mb-4">
<input type="checkbox" id="add-profile-homescreen" checked>
<span>{{ .strings.addProfileStoreHomescreenLayout }}</span>
</label>
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.create }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.create }}</span>
</label>
</form>
</div>
<div id="modal-update" class="modal">
<div class="modal-content wide card">
<div class="relative mx-auto my-[10%] w-4/5 lg:w-2/3 content card">
<span class="heading">{{ .strings.updates }} <span class="modal-close">&times;</span></span>
<p class="content">
<h2>
<a id="update-version"></a> (<span class="monospace" id="update-commit"></span>)
<h2 class="mt-2">
<a id="update-version"></a> (<span class="font-mono bg-inherit" id="update-commit"></span>)
</h2>
<p class="content" id="update-description"></p>
<p class="support" id="update-date"></p>
<div class="content markdown-box" id="update-changelog"></div>
<p class="content mt-2" id="update-description"></p>
<p class="support mt-2" id="update-date"></p>
<div class="content markdown-box mt-2" id="update-changelog"></div>
</p>
<span class="button ~info !normal full-width center" id="update-download">{{ .strings.download }}</span>
<span class="button ~urge !normal full-width center" id="update-update">{{ .strings.update }}</span>
<span class="button ~info @low full-width center mt-2" id="update-download">{{ .strings.download }}</span>
<span class="button ~urge @low full-width center mt-2" id="update-update">{{ .strings.update }}</span>
</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>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
<p class="content mb-4">{{ .strings.sendPIN }}</p>
<h1 class="ac" id="telegram-pin"></h1>
<a class="subheading link-center" id="telegram-link" target="_blank">
<span class="shield ~info mr-1">
<span class="shield ~info mr-2">
<span class="icon">
<i class="ri-telegram-line"></i>
</span>
</span>
&#64;<span id="telegram-username">
</a>
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">{{ .strings.success }}</span>
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
{{ if .discordEnabled }}
<div id="modal-discord" class="modal">
<div class="modal-content card">
<span class="heading mb-1"><span id="discord-header"></span><span class="modal-close">&times;</span></span>
<p class="content mb-1" id="discord-description"></p>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4"><span id="discord-header"></span><span class="modal-close">&times;</span></span>
<p class="content mb-4" id="discord-description"></p>
<div class="row">
<input type="search" class="col sm field ~neutral !normal input" id="discord-search" placeholder="user#1234">
<input type="search" class="col sm field ~neutral @low input" id="discord-search" placeholder="user#1234">
</div>
<table class="table"><tbody id="discord-list"></tbody></table>
</div>
</div>
{{ end }}
<div id="modal-matrix" class="modal">
<form class="modal-content card" id="form-matrix" href="">
<form class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3" id="form-matrix" href="">
<span class="heading">{{ .strings.linkMatrix }}</span>
<p class="content">{{ .strings.linkMatrixDescription }}</p>
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
<input type="text" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="matrix-user">
<input type="password" class="field input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="matrix-password">
<p class="content my-4">{{ .strings.linkMatrixDescription }}</p>
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.matrixHomeServer }}" id="matrix-homeserver">
<input type="text" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.username }}" id="matrix-user">
<input type="password" class="field input ~neutral @high mt-4 mb-2" placeholder="{{ .strings.password }}" id="matrix-password">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.submit }}</span>
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</form>
</div>
<div id="notification-box"></div>
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-1 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral !low">
<label class="switch pb-1">
<input type="radio" name="lang-time" id="lang-12h">
<span>{{ .strings.time12h }}</span>
</label>
<label class="switch pb-1">
<input type="radio" name="lang-time" id="lang-24h">
<span>{{ .strings.time24h }}</span>
</label>
<div id="lang-list"></div>
<div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-2 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<label class="switch pb-4">
<input type="radio" name="lang-time" id="lang-12h">
<span>{{ .strings.time12h }}</span>
</label>
<label class="switch pb-4">
<input type="radio" name="lang-time" id="lang-24h">
<span>{{ .strings.time24h }}</span>
</label>
<div id="lang-list"></div>
</div>
</div>
</div>
</span>
</span>
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
</div>
<div class="page-container">
<div class="mb-1">
<div class="mb-4">
<header class="flex flex-wrap items-center justify-between">
<div class="text-neutral-700">
<span id="button-tab-invites" class="tab-button portal">{{ .strings.invites }}</span>
<span id="button-tab-accounts" class="tab-button portal">{{ .strings.accounts }}</span>
<span id="button-tab-settings" class="tab-button portal">{{ .strings.settings }}</span>
<div>
<span id="button-tab-invites" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.invites }}</span>
<span id="button-tab-accounts" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.accounts }}</span>
<span id="button-tab-settings" class="text-3xl button portal ~neutral dark:~d_neutral @low mr-2 mb-2 px-5">{{ .strings.settings }}</span>
</div>
</header>
</div>
<div class="mb-1">
<div class="text-neutral-700">
<span class="button ~critical !normal mb-1 unfocused" id="logout-button">{{ .strings.logout }}</span>
<span id="button-theme" class="button ~neutral !normal mb-1">{{ .strings.theme }}</span>
<div class="mb-4">
<div>
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
</div>
</div>
<div id="tab-invites">
<div class="card ~neutral !low invites mb-1">
<div class="card @low invites dark:~d_neutral mb-4">
<span class="heading">{{ .strings.invites }}</span>
<div id="invites"></div>
</div>
<div class="card ~neutral !low">
<div class="card @low dark:~d_neutral">
<span class="heading">{{ .strings.create }}</span>
<div class="row" id="create-inv">
<div class="card ~neutral !normal col">
<div class="row mb-1">
<label class="col mr-1">
<div class="flex flex-col md:flex-row gap-3" id="create-inv">
<div class="card ~neutral @low col">
<div class="row mb-2">
<label class="col mr-2">
<input type="radio" name="duration" class="unfocused" id="radio-inv-duration" checked>
<span class="button ~neutral !high supra full-width center">{{ .strings.inviteDuration }}</span>
<span class="button ~neutral @high supra full-width center">{{ .strings.inviteDuration }}</span>
</label>
<label class="col ml-1">
<label class="col ml-2">
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
<span class="button ~neutral !normal supra full-width center">{{ .strings.userExpiry }}</span>
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
</label>
</div>
<div id="inv-duration">
<div class="row">
<div class="col">
<label class="label supra" for="create-months">{{ .strings.inviteMonths }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-months">
<option>0</option>
</select>
@@ -434,7 +466,7 @@
</div>
<div class="col">
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-days">
<option>0</option>
</select>
@@ -444,7 +476,7 @@
<div class="row">
<div class="col">
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-hours">
<option>0</option>
</select>
@@ -452,7 +484,7 @@
</div>
<div class="col">
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-minutes">
<option>0</option>
</select>
@@ -461,17 +493,17 @@
</div>
</div>
<div id="user-expiry" class="unfocused">
<p class="support">{{ .strings.userExpiryDescription }}</p>
<div class="mb-half">
<label for="create-user-expiry-enabled" class="button ~neutral !normal">
<p class="support mb-2">{{ .strings.userExpiryDescription }}</p>
<div class="mb-2">
<label for="create-user-expiry-enabled" class="button ~neutral @low">
<input type="checkbox" id="create-user-expiry-enabled" aria-label="User duration enabled">
<span class="ml-half">{{ .strings.enabled }} </span>
<span class="ml-2">{{ .strings.enabled }} </span>
</label>
</div>
<div class="row">
<div class="col">
<label class="label supra" for="user-months">{{ .strings.inviteMonths }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-months">
<option>0</option>
</select>
@@ -479,7 +511,7 @@
</div>
<div class="col">
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-days">
<option>0</option>
</select>
@@ -489,7 +521,7 @@
<div class="row">
<div class="col">
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-hours">
<option>0</option>
</select>
@@ -497,7 +529,7 @@
</div>
<div class="col">
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-minutes">
<option>0</option>
</select>
@@ -507,87 +539,111 @@
</div>
<div class="col">
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
<input type="text" id="create-label" class="input ~neutral !normal mb-1 mt-half">
<input type="text" id="create-label" class="input ~neutral @low mb-2 mt-4">
</div>
</div>
<div class="card ~neutral !normal col">
<div class="card ~neutral @low col">
<label class="label supra" for="create-uses">{{ .strings.inviteNumberOfUses }}</label>
<div class="flex-expand mb-1 mt-half">
<input type="number" min="0" id="create-uses" class="input ~neutral !normal mr-1" value=1>
<label for="create-inf-uses" class="button ~neutral !normal" title="Set uses to infinite">
<span></span>
<div class="flex-expand mb-2 mt-4">
<input type="number" min="0" id="create-uses" class="input ~neutral @low mr-2" value=1>
<label for="create-inf-uses" class="button ~neutral @low" title="Set uses to infinite">
<span>&infin;</span>
<input type="checkbox" class="unfocused" id="create-inf-uses" aria-label="Set uses to infinite">
</label>
</div>
<p class="support unfocused" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
<p class="support unfocused my-2" id="create-inf-uses-warning"><span class="badge ~critical">{{ .strings.warning }}</span> {{ .strings.inviteInfiniteUsesWarning }}</p>
<label class="label supra">{{ .strings.profile }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-profile">
</select>
</div>
<div id="create-send-to-container">
<label class="label supra">{{ .strings.inviteSendToEmail }}</label>
<div class="flex-expand mb-1 mt-half">
<div class="flex-expand mb-2 mt-4">
{{ if .discordEnabled }}
<input type="text" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com | user#1234">
<span id="create-send-to-search" class="button ~neutral !normal mr-1">
<input type="text" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com | user#1234">
<span id="create-send-to-search" class="button ~neutral @low mr-2">
<i class="icon ri-search-2-line" title="{{ .strings.search }}"></i>
</span>
{{ else }}
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
<input type="email" id="create-send-to" class="input ~neutral @low mr-2" placeholder="example@example.com">
{{ end }}
<label for="create-send-to-enabled" class="button ~neutral !normal">
<label for="create-send-to-enabled" class="button ~neutral @low">
<input type="checkbox" id="create-send-to-enabled" aria-label="Send to address enabled">
</label>
</div>
</div>
<span class="button ~urge !normal supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
<span class="button ~urge @low supra full-width center lg" id="create-submit">{{ .strings.create }}</span>
</div>
</div>
</div>
</div>
<div id="tab-accounts" class="unfocused">
<div class="card ~neutral !low accounts mb-1">
<div class="flex-expand row">
<div class="row">
<span class="heading mr-1 col sm">{{ .strings.accounts }}</span>
<input type="search" class="col sm field ~neutral !normal input search ml-1 mr-1" id="accounts-search" placeholder="{{ .strings.search }}">
</div>
<div class="row">
<span class="col sm button ~neutral !normal center mb-half" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<div id="accounts-announce-dropdown" class="col sm dropdown" tabindex="0">
<span class="h-100 sm button ~info !normal center mb-half" id="accounts-announce">{{ .strings.announce }}</span>
<div class="dropdown-display">
<div class="card ~neutral !low">
<span class="supra sm">{{ .strings.templates }}</span>
<div id="accounts-announce-templates"></div>
</div>
<div class="card @low dark:~d_neutral accounts mb-4 overflow-visible">
<div class="flex-expand align-middle">
<span class="text-3xl font-bold mr-4">{{ .strings.accounts }}</span>
<div id="accounts-filter-dropdown" class="dropdown z-10" tabindex="0">
<span class="h-100 button ~neutral @low center" id="accounts-filter-button">{{ .strings.filters }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low mt-2" id="accounts-filter-list">
<p class="supra pb-2">{{ .strings.filters }}</p>
</div>
</div>
<span class="col sm button ~urge !normal center mb-half" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="col sm button ~warning !normal center mb-half" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
<span class="col sm button ~positive !normal center mb-half" id="accounts-disable-enable">{{ .strings.disable }}</span>
<span class="col sm button ~critical !normal center mb-half" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
<span class="button ~neutral @low center -ml-8" id="accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
</div>
<div class="card ~neutral !normal accounts-header table-responsive mt-half">
<table class="table">
<div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
<div class="row -mx-2">
<button type="button" class="button ~neutral @low center m-2 hidden"><span id="accounts-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
<span id="accounts-filter-area"></span>
</div>
<div class="supra py-1 sm">{{ .strings.actions }}</div>
<div class="row -mx-2">
<span class="col button ~neutral @low center max-w-[20%]" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<div id="accounts-announce-dropdown" class="col dropdown pb-0i max-w-[20%]" tabindex="0">
<span class="w-100 button ~info @low center" id="accounts-announce">{{ .strings.announce }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="supra sm">{{ .strings.templates }}</span>
<div id="accounts-announce-templates"></div>
</div>
</div>
</div>
<span class="col button ~urge @low center max-w-[20%]" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="col button ~warning @low center max-w-[20%]" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
<div id="accounts-disable-enable-dropdown" class="col dropdown manual pb-0i max-w-[20%]" tabindex="0">
<span class="w-100 button ~positive @low center" id="accounts-disable-enable">{{ .strings.disable }}</span>
<div class="dropdown-display">
<div class="card ~neutral @low">
<span class="button ~urge full-width accounts-announce-template-button" id="accounts-enable-expiry">{{ .strings.setExpiry }}</span>
</div>
</div>
</div>
<span class="col button ~info @low center unfocused max-w-[20%]" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
<span class="col button ~critical @low center max-w-[20%]" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
</div>
<div class="card @low accounts-header table-responsive mt-2">
<table class="table text-base leading-4">
<thead>
<tr>
<th><input type="checkbox" value="" id="accounts-select-all"></th>
<th>{{ .strings.username }}</th>
<th>{{ .strings.emailAddress }}</th>
<th class="table-inline my-2 grid gap-4 place-items-stretch accounts-header-username">{{ .strings.username }}</th>
{{ if .jellyfinLogin }}
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-access-jfa">{{ .strings.accessJFA }}</th>
{{ end }}
<th class="grid gap-4 place-items-stretch accounts-header-email">{{ .strings.emailAddress }}</th>
{{ if .telegramEnabled }}
<th>Telegram</th>
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-telegram">Telegram</th>
{{ end }}
{{ if .matrixEnabled }}
<th>Matrix</th>
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-matrix">Matrix</th>
{{ end }}
{{ if .discordEnabled }}
<th>Discord</th>
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-discord">Discord</th>
{{ end }}
<th>{{ .strings.expiry }}</th>
<th>{{ .strings.lastActiveTime }}</th>
<th class="grid gap-4 place-items-stretch accounts-header-expiry">{{ .strings.expiry }}</th>
<th class="grid gap-4 place-items-stretch accounts-header-last-active">{{ .strings.lastActiveTime }}</th>
</tr>
</thead>
<tbody id="accounts-list"></tbody>
@@ -596,27 +652,28 @@
</div>
</div>
<div id="tab-settings" class="unfocused">
<div class="card ~neutral !low settings overflow">
<div class="card @low dark:~d_neutral settings overflow">
<div class="flex-expand">
<div class="flex-row">
<span class="heading">{{ .strings.settings }}</span>
<label for="settings-advanced-enabled" class="button ~neutral !normal ml-1">
<label for="settings-advanced-enabled" class="button ~neutral @low ml-2 my-2">
<input type="checkbox" id="settings-advanced-enabled" aria-label="Advanced settings enabled">
<span class="ml-half">{{ .strings.advancedSettings }} </span>
<span class="ml-2">{{ .strings.advancedSettings }} </span>
</label>
</div>
<div>
<span class="button ~neutral !normal" id="settings-restart">{{ .strings.settingsRestart }}</span>
<span class="button ~urge !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
<span class="button ~info @low my-1" id="settings-logs">{{ .strings.logs }}</span>
<span class="button ~neutral @low my-1" id="settings-restart">{{ .strings.settingsRestart }}</span>
<span class="button ~urge @low unfocused my-1" id="settings-save">{{ .strings.settingsSave }}</span>
</div>
</div>
<div class="row">
<div class="card ~neutral !normal col" id="settings-sidebar">
<aside class="aside sm ~info mb-half" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info">R</span> indicates changes require a restart.</aside>
<span class="button ~neutral !low settings-section-button mb-half" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-half"></i></span></span>
<span class="button ~neutral !low settings-section-button mb-half" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-half"></i></span></span>
<div class="flex flex-col md:flex-row gap-3">
<div class="card @low dark:~d_neutral col" id="settings-sidebar">
<aside class="aside sm ~urge dark:~d_info mb-2 @low" id="settings-message">Note: <span class="badge ~critical">*</span> indicates a required field, <span class="badge ~info dark:~d_warning">R</span> indicates changes require a restart.</aside>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
</div>
<div class="card ~neutral !normal col overflow" id="settings-panel"></div>
<div class="card ~neutral @low col overflow" id="settings-panel"></div>
</div>
</div>
</div>

View File

@@ -7,41 +7,37 @@
</head>
<body>
<div class="page-container">
<div class="row">
<div class="col">
<div class="card ~critical sectioned">
<section class="section ~critical">
<span class="heading">Crash report for jfa-go</span>
{{ if .Err }}
<div class="monospace pre-line mt-1 mb-1">
Error: {{ .Err }}
</div>
{{ end }}
<a class="button ~critical mb-1" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
</section>
<section class="section ~neutral !low">
<div class="flex-expand">
<span class="subheading">Full Log</span>
<span class="button ~urge ml-half" id="copy-log">Copy</span>
</div>
<div class="row mb-1">
<label class="col mr-1">
<span class="button ~neutral !high supra full-width center" id="button-log-normal">Normal</span>
</label>
<label class="col mr-1">
<span class="button ~neutral !normal supra full-width center" id="button-log-sanitized">Sanitized</span>
</label>
</div>
<div id="log-normal">
<pre class="monospace pre-line">{{ .Log }}</pre>
</div>
<div id="log-sanitized" class="unfocused">
<p class="subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
<pre class="monospace pre-line">{{ .SanitizedLog }}</pre>
</div>
</section>
<div class="card ~critical sectioned">
<section class="section ~critical">
<span class="heading">Crash report for jfa-go</span>
{{ if .Err }}
<div class="font-mono bg-inherit pre-line mt-4 mb-4">
Error: {{ .Err }}
</div>
</div>
{{ end }}
<a class="button ~critical mb-4" target="_blank" href="https://github.com/hrfee/jfa-go/issues/new/choose">Create an Issue</a>
</section>
<section class="section ~neutral @low">
<div class="flex-expand">
<span class="subheading">Full Log</span>
<span class="button ~urge ml-4" id="copy-log">Copy</span>
</div>
<div class="row mb-4">
<label class="col mr-4">
<span class="button ~neutral @high supra full-width center" id="button-log-normal">Normal</span>
</label>
<label class="col mr-4">
<span class="button ~neutral @low supra full-width center" id="button-log-sanitized">Sanitized</span>
</label>
</div>
<div id="log-normal">
<pre class="font-mono bg-inherit pre-line">{{ .Log }}</pre>
</div>
<div id="log-sanitized" class="unfocused">
<p class="subheading">An attempt has been made to remove sensitive info, but make sure to check yourself.</p>
<pre class="font-mono bg-inherit pre-line">{{ .SanitizedLog }}</pre>
</div>
</section>
</div>
</div>
<script inline src="crash.js"></script>

View File

@@ -1,16 +1,16 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .strings.successHeader }} - jfa-go</title>
<title>{{ .strings.successHeader }} - jfa-go</title>
</head>
<body class="section">
<div class="page-container">
<div class="card ~neutral !normal mb-1">
<span class="heading mb-1">{{ .strings.successHeader }}</span>
<p class="content mb-1">{{ .successMessage }}</p>
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.successContinueButton }}</a>
<div class="card ~neutral @low mb-4">
<span class="heading mb-4">{{ .strings.successHeader }}</span>
<p class="content my-4">{{ .successMessage }}</p>
<a class="button ~urge @high full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
</div>
<i class="content">{{ .contactMessage }}</i>
</div>

View File

@@ -17,6 +17,7 @@
window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }};
window.telegramPIN = "{{ .telegramPIN }}";
window.emailRequired = {{ .emailRequired }};
window.discordEnabled = {{ .discordEnabled }};
window.discordRequired = {{ .discordRequired }};
window.discordPIN = "{{ .discordPIN }}";
@@ -25,6 +26,7 @@
window.matrixEnabled = {{ .matrixEnabled }};
window.matrixRequired = {{ .matrixRequired }};
window.matrixUserID = "{{ .matrixUser }}";
window.captcha = {{ .captcha }};
</script>
{{ if .passwordReset }}
<script src="js/pwr.js" type="module"></script>

View File

@@ -1,99 +1,102 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>
{{ if .passwordReset }}
{{ .strings.passwordReset }}
{{ else }}
{{ .strings.pageTitle }}
{{ end }}
</title>
{{ if .passwordReset }}
<title>{{ .strings.passwordReset }}</title>
{{ else }}
<title>{{ .strings.pageTitle }}</title>
{{ end }}
<script>
window.redirectToJellyfin = {{ .redirectToJellyfin }};
</script>
</head>
<body class="max-w-full overflow-x-hidden section">
<div id="modal-success" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.successHeader }}</span>
<p class="content mb-1">{{ .successMessage }}</p>
<a class="button ~urge !normal full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.successContinueButton }}</a>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ if .passwordReset }}{{ .strings.passwordReset }}{{ else }}{{ .strings.successHeader }}{{ end }}</span>
<p class="content mb-4">{{ if .passwordReset }}{{ .strings.youCanLoginPassword }}{{ else }}{{ .successMessage }}{{ end }}</p>
<a class="button ~urge @low full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
</div>
</div>
<div id="modal-confirmation" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.confirmationRequired }}</span>
<p class="content mb-1">{{ .strings.confirmationRequiredMessage }}</p>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.confirmationRequired }}</span>
<p class="content mb-4">{{ .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>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.linkTelegram }}</span>
<p class="content mb-4">{{ .strings.sendPIN }}</p>
<p class="text-center text-2xl mb-2">{{ .telegramPIN }}</p>
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
<span class="shield ~info mr-1">
<span class="shield ~info mr-4">
<span class="icon">
<i class="ri-telegram-line"></i>
</span>
</span>
&#64;{{ .telegramUsername }}
</a>
<span class="button ~info !normal full-width center mt-1" id="telegram-waiting">{{ .strings.success }}</span>
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
{{ if .discordEnabled }}
<div id="modal-discord" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.linkDiscord }}</span>
<p class="content mb-1"> {{ .discordSendPINMessage }}</p>
<h1 class="ac">{{ .discordPIN }}</h1>
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.linkDiscord }}</span>
<p class="content mb-4"> {{ .discordSendPINMessage }}</p>
<h1 class="text-center text-2xl mb-2">{{ .discordPIN }}</h1>
<a id="discord-invite"></a>
<span class="button ~info !normal full-width center mt-1" id="discord-waiting">{{ .strings.success }}</span>
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
{{ if .matrixEnabled }}
<div id="modal-matrix" class="modal">
<div class="modal-content card">
<span class="heading mb-1">{{ .strings.linkMatrix }}</span>
<p class="content mb-1"> {{ .strings.matrixEnterUser }}</p>
<input type="text" class="input ~neutral !high" placeholder="@user:riot.im" id="matrix-userid">
<div class="subheading link-center mt-1">
<span class="shield ~info mr-1">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<span class="heading mb-4">{{ .strings.linkMatrix }}</span>
<p class="content mb-4"> {{ .strings.matrixEnterUser }}</p>
<input type="text" class="input ~neutral @high" placeholder="@user:riot.im" id="matrix-userid">
<div class="subheading link-center mt-4">
<span class="shield ~info mr-4">
<span class="icon">
<i class="ri-chat-3-line"></i>
</span>
</span>
{{ .matrixUser }}
</div>
<span class="button ~info !normal full-width center mt-1" id="matrix-send">{{ .strings.submit }}</span>
<span class="button ~info @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
</div>
</div>
{{ end }}
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-1 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral !low" id="lang-list">
<div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-2 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral @low" id="lang-list">
</div>
</div>
</div>
</span>
</span>
</div>
<div id="notification-box"></div>
<div class="page-container">
<div class="card ~neutral !low">
<div class="row baseline">
<span class="col heading">
<div class="card dark:~d_neutral @low">
<div class="flex flex-col md:flex-row gap-3 baseline">
<span class="heading mr-5">
{{ if .passwordReset }}
{{ .strings.passwordReset }}
{{ else }}
{{ .strings.createAccountHeader }}
{{ end }}
</span>
<span class="col subheading">
<span class="subheading">
{{ if .passwordReset }}
{{ .strings.enterYourPassword }}
{{ else }}
@@ -101,60 +104,60 @@
{{ end }}
</span>
</div>
<div class="row">
<div class="col">
<div class="flex flex-col md:flex-row gap-3">
<div class="flex-1">
{{ if .userExpiry }}
<aside class="col aside sm ~warning" id="user-expiry-message"></aside>
{{ end }}
<form class="card ~neutral !normal" id="form-create" href="">
<form class="card dark:~d_neutral @low" id="form-create" href="">
{{ if not .passwordReset }}
<label class="label supra">
{{ .strings.username }}
<input type="text" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
<input type="text" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.username }}" id="create-username" aria-label="{{ .strings.username }}">
</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-2 mb-4" placeholder="{{ .strings.emailAddress }}" id="create-email" aria-label="{{ .strings.emailAddress }}" value="{{ .email }}">
{{ if .telegramEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-telegram">{{ .strings.linkTelegram }}</span>
<span class="button ~info @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }} {{ if .telegramRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }}
{{ if .discordEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-discord">{{ .strings.linkDiscord }}</span>
<span class="button ~info @low full-width center mb-4" id="link-discord">{{ .strings.linkDiscord }} {{ if .discordRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }}
{{ if .matrixEnabled }}
<span class="button ~info !normal full-width center mb-1" id="link-matrix">{{ .strings.linkMatrix }}</span>
<span class="button ~info @low full-width center mb-4" id="link-matrix">{{ .strings.linkMatrix }} {{ if .matrixRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }}
{{ if or (.telegramEnabled) (or .discordEnabled .matrixEnabled) }}
<div id="contact-via" class="unfocused">
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="email"><span>Contact through Email</span>
<label class="row switch pb-4 unfocused">
<input type="radio" name="contact-via" value="email" id="contact-via-email" class="mr-2"><span>Contact through Email</span>
</label>
{{ if .telegramEnabled }}
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="telegram" id="contact-via-telegram"><span>Contact through Telegram</span>
<label class="row switch pb-4 unfocused">
<input type="radio" name="contact-via" value="telegram" id="contact-via-telegram" class="mr-2"><span>Contact through Telegram</span>
</label>
{{ end }}
{{ if .discordEnabled }}
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="discord" id="contact-via-discord"><span>Contact through Discord</span>
<label class="row switch pb-4 unfocused">
<input type="radio" name="contact-via" value="discord" id="contact-via-discord" class="mr-2"><span>Contact through Discord</span>
</label>
{{ end }}
{{ if .matrixEnabled }}
<label class="row switch pb-1">
<input type="radio" name="contact-via" value="matrix" id="contact-via-matrix"><span>Contact through Matrix</span>
<label class="row switch pb-4 unfocused">
<input type="radio" name="contact-via" value="matrix" id="contact-via-matrix" class="mr-2"><span>Contact through Matrix</span>
</label>
{{ end }}
</div>
{{ end }}
{{ 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 }}">
<input type="password" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.password }}" id="create-password" aria-label="{{ .strings.password }}">
<label class="label supra" for="create-reenter-password">{{ .strings.reEnterPassword }}</label>
<input type="password" class="input ~neutral !high mt-half mb-1" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
<input type="password" class="input ~neutral @high mt-2 mb-4" placeholder="{{ .strings.password }}" id="create-reenter-password" aria-label="{{ .strings.reEnterPassword }}">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">
<span class="button ~urge @low full-width center supra submit">
{{ if .passwordReset }}
{{ .strings.reset }}
{{ else }}
@@ -164,8 +167,8 @@
</label>
</form>
</div>
<div class="col">
<div class="card ~neutral !normal">
<div class="flex-1">
<div class="card ~neutral @low mb-4">
<span class="label supra" for="inv-uses">{{ .strings.passwordRequirementsHeader }}</span>
<ul>
{{ range $key, $value := .requirements }}
@@ -175,8 +178,15 @@
{{ end }}
</ul>
</div>
{{ if .captcha }}
<div class="card ~neutral @low mb-4">
<span class="label supra mb-2">CAPTCHA <span id="captcha-regen" title="{{ .strings.refresh }}" class="badge lg @low ~info ml-2 float-right"><i class="ri-refresh-line"></i></span><span id="captcha-success" class="badge lg @low ~critical ml-2 float-right"><i class="ri-close-line"></i></span></span>
<div id="captcha-img" class="mt-2 mb-2"></div>
<input class="field ~neutral @low" id="captcha-input" class="mt-2" placeholder="CAPTCHA">
</div>
{{ end }}
{{ if .contactMessage }}
<aside class="col aside sm ~info">{{ .contactMessage }}</aside>
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
{{ end }}
</div>
</div>

View File

@@ -1,17 +1,19 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>Invalid Code - jfa-go</title>
</head>
<body class="section">
<div class="page-container">
<h1 class="heading">Invalid invite code.</h1>
<p class="content">The code above was either incorrect, or has expired.</p>
<p class="content">
{{ .contactMessage }}
</p>
<div class="card">
<h1 class="text-3xl font-semibold">Invalid invite code.</h1>
<p class="content">The code above was either incorrect, or has expired.</p>
<p class="content">
{{ .contactMessage }}
</p>
</div>
</div>
</body>
</html>

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .strings.passwordReset }} - jfa-go</title>
</head>
@@ -12,15 +12,15 @@
</div>
{{ end }}
<div class="page-container">
<div class="card ~neutral !normal mb-1">
<span class="heading mb-1">
<div class="card ~neutral @low mb-4">
<span class="heading mb-4">
{{ if .success }}
{{ .strings.passwordReset }}
{{ else }}
{{ .strings.resetFailed }}
{{ end }}
</span>
<p class="content mb-1">
<p class="content mb-4">
{{ if .success }}
{{ if .ombiEnabled }}
{{ .strings.youCanLoginOmbi }}
@@ -35,7 +35,7 @@
<aside class="aside ~warning">
{{ .strings.changeYourPassword }}
</aside>
<span class="button ~urge !normal full-width center supra p-1 mt-1" id="pin" title="{{ .strings.copy }}">{{ .pin }}</span>
<span class="button ~urge @low w-100 text-center text-xl p-1 mt-4" id="pin" title="{{ .strings.copy }}">{{ .pin }}</span>
{{ end }}
</div>
<i class="content">{{ .contactMessage }}</i>

View File

@@ -1,24 +1,26 @@
<!DOCTYPE html>
<html lang="en" class="light-theme">
<html lang="en" class="light">
<head>
<link rel="stylesheet" type="text/css" href="css/bundle.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .lang.Strings.pageTitle }}</title>
</head>
<body class="max-w-full overflow-x-hidden section">
<div id="notification-box"></div>
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-1 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral !low" id="lang-list">
<div class="top-4 left-4 absolute">
<span class="dropdown" tabindex="0" id="lang-dropdown">
<span class="button ~urge dropdown-button">
<i class="ri-global-line"></i>
<span class="ml-2 chev"></span>
</span>
<div class="dropdown-display">
<div class="card ~neutral @low" id="lang-list">
</div>
</div>
</div>
</span>
</span>
</div>
<div class="page-container" id="page-container">
<div class="card ~neutral !low mb-1">
<div class="card ~neutral @low mb-2">
<div class="row">
<img class="banner header" src="banner.svg" alt="jfa-go" />
</div>
@@ -26,70 +28,70 @@
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
</div>
<div class="row col flex center">
<p class="content">{{ .lang.StartPage.pressStart }}</p>
<p class="content my-2">{{ .lang.StartPage.pressStart }}</p>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="support">{{ .lang.StartPage.httpsNotice }}</span>
<span class="button ~urge !normal next">{{ .lang.StartPage.start }}</span>
<span class="button ~urge @low next">{{ .lang.StartPage.start }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Language.title }}</span>
<p class="content" id="language-description"></p>
<p class="content my-2" id="language-description"></p>
<label class="label">
<span class="mt-half">{{ .lang.Language.defaultAdminLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<span class="mt-4">{{ .lang.Language.defaultAdminLang }}</span>
<div class="select ~neutral @low mt-4 mb-2">
<select id="ui-language-admin">
</select>
</div>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Language.defaultFormLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<span class="mt-4">{{ .lang.Language.defaultFormLang }}</span>
<div class="select ~neutral @low mt-4 mb-2">
<select id="ui-language-form">
</select>
</div>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Language.defaultEmailLang }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<span class="mt-4">{{ .lang.Language.defaultEmailLang }}</span>
<div class="select ~neutral @low mt-4 mb-2">
<select id="email-language">
</select>
</div>
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.General.title }}</span>
<div class="row">
<div class="col">
<label class="label">
<span class="mt-half">{{ .lang.General.listenAddress }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ui-host" value="0.0.0.0">
<span class="mt-4">{{ .lang.General.listenAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ui-host" value="0.0.0.0">
</label>
<label class="row switch">
<input type="checkbox" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
<input type="checkbox" class="mr-2" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
</label>
<p class="support mb-1">{{ .lang.General.useHTTPSNotice }}</p>
<p class="support mb-2 mt-1">{{ .lang.General.useHTTPSNotice }}</p>
<label class="label">
<span class="mt-half">{{ .lang.General.pathToCertificate }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_cert">
<span class="mt-4">{{ .lang.General.pathToCertificate }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_cert">
</label>
<label class="label">
<span class="mt-half">{{ .lang.General.pathToKeyFile }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_key">
<span class="mt-4">{{ .lang.General.pathToKeyFile }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_key">
</label>
<span class="heading">{{ .lang.Updates.title }}</span>
<p class="content" id="updates-description"></p>
<label class="row switch pb-1">
<input type="checkbox" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2" id="updates-description"></p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="updates-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span>{{ .lang.Updates.updateChannel }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<div class="select ~neutral @low mt-4 mb-2">
<select id="updates-channel">
<option value="stable">{{ .lang.Updates.stable }}</option>
<option value="unstable">{{ .lang.Updates.unstable }}</option>
@@ -99,21 +101,21 @@
</div>
<div class="col">
<label class="label">
<span class="mt-half">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="ui-port" value="8056">
<span class="mt-4">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="ui-port" value="8056">
</label>
<label class="label">
<span class="mt-half">{{ .lang.General.httpsPort }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="advanced-tls_port" value="8057">
<span class="mt-4">{{ .lang.General.httpsPort }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="advanced-tls_port" value="8057">
</label>
<label class="label">
<span class="mt-half">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
<input type="url" class="input ~neutral !normal mt-half" id="ui-url_base">
<p class="support mb-1">{{ .lang.General.urlBaseNotice }}</p>
<span class="mt-4">{{ .lang.General.urlBase }} ({{ .lang.Strings.optional }})</span>
<input type="url" class="input ~neutral @low mt-4" id="ui-url_base">
<p class="support mb-2 mt-1">{{ .lang.General.urlBaseNotice }}</p>
</label>
<label class="label">
<span>{{ .lang.Strings.theme }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<div class="select ~neutral @low mt-4 mb-2">
<select id="ui-theme">
<option value="Jellyfin (Dark)">{{ .lang.General.darkTheme }}</option>
<option value="Default (Light)">{{ .lang.General.lightTheme }}</option>
@@ -123,142 +125,146 @@
</div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Login.title }}</span>
<p class="content">{{ .lang.Login.description }}</p>
<div class="pl-1">
<label class="row switch pb-1">
<input type="radio" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
<p class="content my-2">{{ .lang.Login.description }}</p>
<div class="pl-4">
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="true" checked><span>{{ .lang.Login.authorizeWithJellyfin }}</span>
</label>
<label class="row switch pl-1 pb-1">
<input type="checkbox" id="ui-admin_only"><span>{{ .lang.Login.adminOnly }}</span>
<label class="row switch pl-4 pb-4">
<input type="checkbox" class="mr-2" id="ui-admin_only" checked><span>{{ .lang.Login.adminOnly }}</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
<label class="row switch pl-4 pb-2">
<input type="checkbox" class="mr-2" id="ui-allow_all"><span>{{ .lang.Login.allowAll }}</span>
</label>
<p class="support pb-4 pl-4 mt-1" id="description-ui-allow_all">{{ .lang.Login.allowAllDescription }}</p>
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="ui-jellyfin_login" value="false"><span>{{ .lang.Login.authorizeManual }}</span>
</label>
</div>
<div id="login-manual">
<label class="label">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" id="ui-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
<span class="mt-4">{{ .lang.Strings.username }}</span>
<input type="text" id="ui-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label">
<span>{{ .lang.Strings.password }}</span>
<input type="password" id="ui-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
<input type="password" id="ui-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}">
</label>
<label class="label">
<span>{{ .lang.Strings.emailAddress }} ({{ .lang.Strings.optional }})</span>
<input type="email" id="ui-email" class="input ~neutral !normal mt-half" placeholder="email@address">
<span class="support mb-1">{{ .lang.Login.emailNotice }}</span>
<input type="email" id="ui-email" class="input ~neutral @low mt-4" placeholder="email@address">
<span class="support mb-2 mt-1">{{ .lang.Login.emailNotice }}</span>
</label>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
<p class="content">{{ .lang.JellyfinEmby.description }}</p>
<p class="content my-2">{{ .lang.JellyfinEmby.description }}</p>
<div class="row">
<div class="col">
<label class="label">
<span>{{ .lang.Strings.serverType }}</span>
<div class="select ~neutral !normal mt-half">
<div class="select ~neutral @low mt-4">
<select id="jellyfin-type">
<option value="jellyfin">Jellyfin</option>
<option value="emby">Emby</option>
</select>
</div>
<p class="support mb-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
<input type="text" class="input ~neutral !normal mt-half" id="jellyfin-substitute_jellyfin_strings">
<p class="support mb-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
<span class="mt-4">{{ .lang.JellyfinEmby.replaceJellyfin }} ({{ .lang.Strings.optional }})</span>
<input type="text" class="input ~neutral @low mt-4" id="jellyfin-substitute_jellyfin_strings">
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.replaceJellyfinNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" id="jellyfin-username" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.username }}">
<span class="mt-4">{{ .lang.Strings.username }}</span>
<input type="text" id="jellyfin-username" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.username }}">
</label>
<label class="label">
<span>{{ .lang.Strings.password }}</span>
<input type="password" id="jellyfin-password" class="input ~neutral !normal mt-half mb-1" placeholder="{{ .lang.Strings.password }}">
<input type="password" id="jellyfin-password" class="input ~neutral @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}">
</label>
</div>
<div class="col">
<label class="label">
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="jellyfin-server" placeholder="http://jellyf.in:80">
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.internal }})</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="jellyfin-server" placeholder="http://jellyf.in:80">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
<input type="url" class="input ~neutral !normal mt-half" id="jellyfin-public_server" placeholder="https://jellyf.in">
<p class="support mb-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
<span class="mt-4">{{ .lang.Strings.serverAddress }} ({{ .lang.JellyfinEmby.external }})</span>
<input type="url" class="input ~neutral @low mt-4" id="jellyfin-public_server" placeholder="https://jellyf.in">
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.addressExternalNotice }}</p>
</label>
</div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
<span class="button ~urge !normal next" disabled>{{ .lang.Strings.next }}</span>
<span class="button ~urge @low" id="jellyfin-test-connection">{{ .lang.JellyfinEmby.testConnection }}</span>
<span class="button ~urge @low next" disabled>{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Ombi.title }}</span>
<p class="content">{{ .lang.Ombi.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.Ombi.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="ombi-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="ombi-server" placeholder="ombi.jellyf.in">
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="ombi-server" placeholder="ombi.jellyf.in">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ombi-api_key">
<p class="support mb-1">{{ .lang.Ombi.apiKeyNotice }}</p>
<span class="mt-4">{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ombi-api_key">
<p class="support mb-2 mt-1">{{ .lang.Ombi.apiKeyNotice }}</p>
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Messages.title }}</span>
<p class="content" id="messages-description"></p>
<label class="row switch pb-1">
<input type="checkbox" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2" id="messages-description"></p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="messages-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Email.dateFormat }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-date_format" value="%d/%m/%y">
<p class="support mb-1" id="email-dateformat-notice"></p>
<span class="mt-4">{{ .lang.Email.dateFormat }}</span>
<input type="text" class="input ~neutral @low mt-4" id="email-date_format" value="%d/%m/%y">
<p class="support mb-2 mt-1" id="email-dateformat-notice"></p>
</label>
<div>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
</label>
<label class="row switch pb-1">
<input type="radio" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
<label class="row switch pb-4">
<input type="radio" class="mr-2" name="email-24h" value="false"><span>{{ .lang.Strings.time12h }}</span>
</label>
</div>
<div id="email-sect">
<span class="heading">{{ .lang.Email.title }}</span>
<p class="content" id="email-description"></p>
<p class="content my-2" id="email-description"></p>
<div class="row">
<div class="col">
<label class="label">
<span>{{ .lang.Email.method }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<div class="select ~neutral @low mt-4 mb-2">
<select id="email-method">
<option value="">{{ .lang.Strings.disabled }}</option>
<option value="smtp">SMTP</option>
@@ -267,24 +273,24 @@
</div>
</label>
<label class="row switch">
<input type="checkbox" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
<p class="support mb-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
<input type="checkbox" class="mr-2" id="email-no_username"><span>{{ .lang.Email.useEmailAsUsername }}</span>
<p class="support mb-2 mt-1">{{ .lang.Email.useEmailAsUsernameNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Email.fromAddress }}</span>
<input type="email" class="input ~neutral !normal mt-half mb-1" id="email-address" placeholder="mail@jellyf.in">
<span class="mt-4">{{ .lang.Email.fromAddress }}</span>
<input type="email" class="input ~neutral @low mt-4 mb-2" id="email-address" placeholder="mail@jellyf.in">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="email-from" value="Jellyfin">
<span class="mt-4">{{ .lang.Email.senderName }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="email-from" value="Jellyfin">
</label>
</div>
<div class="col">
<div id="email-smtp">
<p class="subheading">SMTP</p>
<p class="text-2xl font-semibold mb-2">SMTP</p>
<label class="label">
<span>{{ .lang.Email.encryption }}</span>
<div class="select ~neutral !normal mt-half mb-1">
<div class="select ~neutral @low mt-4 mb-2">
<select id="smtp-encryption">
<option value="starttls">STARTTLS ({{ .lang.Strings.port }} 587)</option>
<option value="ssl_tls">SSL/TLS ({{ .lang.Strings.port }} 465)</option>
@@ -292,197 +298,197 @@
</div>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="smtp-server" placeholder="smtp.jellyf.in">
<span class="mt-4">{{ .lang.Strings.serverAddress }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="smtp-server" placeholder="smtp.jellyf.in">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="smtp-port" placeholder="587">
<span class="mt-4">{{ .lang.Strings.port }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="smtp-port" placeholder="587">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="smtp-username">
<span class="mt-4">{{ .lang.Strings.username }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="smtp-username">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral !normal mt-half mb-1" id="smtp-password">
<span class="mt-4">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral @low mt-4 mb-2" id="smtp-password">
</label>
</div>
<div id="email-mailgun">
<p class="subheading">Mailgun</p>
<p class="text-2xl font-semibold mb-2">Mailgun</p>
<label class="label">
<span class="mt-half">{{ .lang.Email.mailgunApiURL }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
<span class="mt-4">{{ .lang.Email.mailgunApiURL }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_url" placeholder="https://api.eu.mailgun.net/v3/mail.jellyf.in/messages">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="mailgun-api_key">
<span class="mt-4">{{ .lang.Strings.apiKey }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="mailgun-api_key">
</label>
</div>
</div>
</div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused related-to-email">
<div class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.Notifications.title }}</span>
<p class="content">{{ .lang.Notifications.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.Notifications.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="notifications-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<span class="heading">{{ .lang.WelcomeEmails.title }}</span>
<p class="content">{{ .lang.WelcomeEmails.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.WelcomeEmails.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="welcome_email-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="welcome_email-subject" placeholder="{{ .emailLang.WelcomeEmail.title }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused related-to-email">
<div class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.InviteEmails.title }}</span>
<p class="content">{{ .lang.InviteEmails.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.InviteEmails.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="invite_emails-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.URL }}</span>
<input type="url" class="input ~neutral !normal mt-half mb-1" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
<span class="mt-4">{{ .lang.Strings.URL }}</span>
<input type="url" class="input ~neutral @low mt-4 mb-2" id="invite_emails-url_base" placeholder="https://accounts.jellyf.in/invite">
</label>
<label class="label">
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="invite_emails-subject" placeholder="{{ .emailLang.InviteEmail.title }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div id="password-resets" class="card ~neutral !low mb-1 unfocused related-to-email">
<div id="password-resets" class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.PasswordResets.title }}</span>
<p class="content">{{ .lang.PasswordResets.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.PasswordResets.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="password_resets-enabled"><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordResets.pathToJellyfin }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="password_resets-watch_directory" placeholder="/config/jellyfin">
<p class="support mb-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
<span class="mt-4">{{ .lang.PasswordResets.pathToJellyfin }}</span>
<input type="text" class="input ~neutral @low mt-4" id="password_resets-watch_directory" placeholder="/config/jellyfin">
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.pathToJellyfinNotice }}</p>
</label>
<label class="switch">
<input type="checkbox" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
<p class="support mb-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
<input type="checkbox" class="mr-2" id="password_resets-link_reset"><span>{{ .lang.PasswordResets.resetLinks }}</span>
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.resetLinksNotice }}</p>
</label>
<label class="switch">
<input type="checkbox" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
<p class="support mb-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
<input type="checkbox" class="mr-2" id="password_resets-set_password"><span>{{ .lang.PasswordResets.setPassword }}</span>
<p class="support mb-2 mt-1">{{ .lang.PasswordResets.setPasswordNotice }}</p>
</label>
<label class="label">
<p class="mt-half">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
<div class="select ~neutral !normal mt-half mb-1">
<p class="mt-4">{{ .lang.PasswordResets.resetLinksLanguage }}</p>
<div class="select ~neutral @low mt-4 mb-2">
<select id="password_resets-language">
</select>
</div>
</label>
<label class="row label">
<span class="mt-half">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral !normal mt-half mb-1" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
<span class="mt-4">{{ .lang.Strings.emailSubject }}</span>
<input type="text" class="input ~neutral @low mt-4 mb-2" id="password_resets-subject" placeholder="{{ .emailLang.PasswordReset.title }}">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.PasswordValidation.title }}</span>
<p class="content">{{ .lang.PasswordValidation.description }}</p>
<label class="row switch pb-1">
<input type="checkbox" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
<p class="content my-2">{{ .lang.PasswordValidation.description }}</p>
<label class="row switch pb-4">
<input type="checkbox" class="mr-2" id="password_validation-enabled" checked><span>{{ .lang.Strings.enabled }}</span>
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordValidation.length }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-min_length" value="8">
<span class="mt-4">{{ .lang.PasswordValidation.length }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-min_length" value="8">
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordValidation.uppercase }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-upper" value="1">
<span class="mt-4">{{ .lang.PasswordValidation.uppercase }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-upper" value="1">
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordValidation.lowercase }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-lower" value="0">
<span class="mt-4">{{ .lang.PasswordValidation.lowercase }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-lower" value="0">
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordValidation.numbers }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-number" value="0">
<span class="mt-4">{{ .lang.PasswordValidation.numbers }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-number" value="0">
</label>
<label class="label">
<span class="mt-half">{{ .lang.PasswordValidation.special }}</span>
<input type="number" class="input ~neutral !normal mt-half mb-1" id="password_validation-special" value="0">
<span class="mt-4">{{ .lang.PasswordValidation.special }}</span>
<input type="number" class="input ~neutral @low mt-4 mb-2" id="password_validation-special" value="0">
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.HelpMessages.title }}</span>
<p class="content">{{ .lang.HelpMessages.description }}</p>
<p class="content my-2">{{ .lang.HelpMessages.description }}</p>
<label class="label">
<span class="mt-half">{{ .lang.HelpMessages.contactMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-contact_message">
<p class="support mb-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
<span class="mt-4">{{ .lang.HelpMessages.contactMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-contact_message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.contactMessageNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.HelpMessages.helpMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-help_message">
<p class="support mb-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
<span class="mt-4">{{ .lang.HelpMessages.helpMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-help_message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.helpMessageNotice }}</p>
</label>
<label class="label">
<span class="mt-half">{{ .lang.HelpMessages.successMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="ui-success_message">
<p class="support mb-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
<span class="mt-4">{{ .lang.HelpMessages.successMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="ui-success_message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.successMessageNotice }}</p>
</label>
<label class="label related-to-email">
<span class="mt-half">{{ .lang.HelpMessages.emailMessage }}</span>
<input type="text" class="input ~neutral !normal mt-half" id="email-message">
<p class="support mb-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
<span class="mt-4">{{ .lang.HelpMessages.emailMessage }}</span>
<input type="text" class="input ~neutral @low mt-4" id="email-message">
<p class="support mb-2 mt-1">{{ .lang.HelpMessages.emailMessageNotice }}</p>
</label>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral !normal back">{{ .lang.Strings.back }}</span>
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge !normal next">{{ .lang.Strings.next }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral !low mb-1 unfocused">
<div class="card ~neutral @low mb-2 unfocused">
<div class="row col flex center">
<span class="heading">{{ .lang.EndPage.finished }}</span>
</div>
<div class="row col flex center">
<p class="content">{{ .lang.EndPage.restartMessage }}</p>
<p class="content my-2">{{ .lang.EndPage.restartMessage }}</p>
</div>
<div class="row col flex center">
<span class="button ~neutral !normal back mr-1">{{ .lang.Strings.back }}</span>
<span class="button ~urge !normal" id="restart">{{ .lang.Strings.submit }}</span>
<span class="button ~urge !normal unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
<span class="button ~neutral @low back mr-4">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low" id="restart">{{ .lang.Strings.submit }}</span>
<span class="button ~urge @low unfocused" id="refresh">{{ .lang.EndPage.refreshPage }}</span>
</div>
</div>
</div>

BIN
images/thumb-white.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

203
images/thumb-white.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 128 KiB

View File

@@ -1,50 +0,0 @@
package main
import "time"
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
type inviteDaemon struct {
Stopped bool
ShutdownChannel chan string
Interval time.Duration
period time.Duration
app *appContext
}
func newInviteDaemon(interval time.Duration, app *appContext) *inviteDaemon {
return &inviteDaemon{
Stopped: false,
ShutdownChannel: make(chan string),
Interval: interval,
period: interval,
app: app,
}
}
func (rt *inviteDaemon) run() {
rt.app.info.Println("Invite daemon started")
for {
select {
case <-rt.ShutdownChannel:
rt.ShutdownChannel <- "Down"
return
case <-time.After(rt.period):
break
}
started := time.Now()
rt.app.storage.loadInvites()
rt.app.debug.Println("Daemon: Checking invites")
rt.app.checkInvites()
finished := time.Now()
duration := finished.Sub(started)
rt.period = rt.Interval - duration
}
}
func (rt *inviteDaemon) Shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel
close(rt.ShutdownChannel)
}

View File

@@ -1,6 +1,6 @@
{
"meta": {
"name": "Dansk"
"name": "Dansk (DK)"
},
"strings": {
"invites": "Invitationer",
@@ -77,7 +77,7 @@
"settingsRequiredOrRestartMessage": "Bemærk: {n} angiver et obligatorisk felt, {n} angiver at ændringer kræver genstart.",
"settingsSave": "Gem",
"ombiUserDefaults": "Ombi bruger standarder",
"ombiUserDefaultsDescription": "Opret en Ombi bruger og konfigurer den, vælg den derefter nedenfor. Brugerens indstillinger/tilladelser gemmes og anvendes på nye Ombi brugere oprettet af jfa-go",
"ombiUserDefaultsDescription": "Opret en Ombi bruger og konfigurer den, vælg den derefter nedenfor. Brugerens indstillinger/tilladelser gemmes og anvendes på nye Ombi brugere oprettet af jfa-go når denne profil er valgt.",
"userProfiles": "Bruger Profiler",
"userProfilesDescription": "Profiler anvendes på brugere når de opretter en konto. En profil inkluderer adgangsrettigheder til biblioteket og layout på startskærmen.",
"userProfilesIsDefault": "Standard",
@@ -104,7 +104,17 @@
"saveAsTemplate": "Gem som skabelon",
"templates": "Skabeloner",
"deleteTemplate": "Slet skabelon",
"templateEnterName": "Indtast et navn for at gemme denne skabelon."
"templateEnterName": "Indtast et navn for at gemme denne skabelon.",
"ombiProfile": "Ombi bruger profil",
"setExpiry": "Sæt udløb",
"logs": "Log",
"sendPWR": "Send Nulstilling af Adgangskode",
"sendPWRManual": "Brugeren {n} har ingen kontaktinformation, tryk kopier for at få et link du kan sende til dem.",
"sendPWRSuccess": "Link til nulstilling af adgangskode sendt.",
"sendPWRSuccessManual": "Hvis brugeren ikke er modtaget den, så tryk på kopier for manuelt at sende et link til dem.",
"sendPWRValidFor": "Dette link er gyldigt i 30m.",
"accessJFA": "Få adgang til jfa-go",
"accessJFASettings": "Kan ikke ændres, da enten \"Kun administrator\" eller \"Tillad alle\" er blevet indstillet i Indstillinger > Generelt."
},
"notifications": {
"changedEmailAddress": "Ændret e-mail adresse på {n}.",
@@ -145,7 +155,9 @@
"errorCheckUpdate": "Kunne ikke kontrollere for opdatering.",
"updateAvailable": "En ny opdatering er tilgængelig, tjek indstillingerne.",
"noUpdatesAvailable": "Ingen nye opdateringer tilgængelige.",
"savedAnnouncement": "Meddelelse gemt."
"savedAnnouncement": "Meddelelse gemt.",
"setOmbiProfile": "Gemt i ombi profilen.",
"errorSetOmbiProfile": "Ombi profilen kunne ikke gemmes."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -199,6 +211,10 @@
"extendedExpiry": {
"singular": "Forlængede udløb for {n} bruger.",
"plural": "Forlængede udløb for {n} brugere."
},
"setExpiry": {
"singular": "Indstil udløb for {n} bruger",
"plural": "Indstil udløb for {n} brugere"
}
}
}

View File

@@ -43,7 +43,7 @@
"settingsRequiredOrRestartMessage": "Hinweis: {n} zeigt ein erforderliches Feld an, {n} zeigt an, dass Änderungen einen Neustart erfordern.",
"settingsSave": "Speichern",
"ombiUserDefaults": "Ombi-Benutzerstandardeinstellungen",
"ombiUserDefaultsDescription": "Erstelle einen Ombi-Benutzer und konfiguriere ihn, dann wähle ihn unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Ombi-Benutzer, erstellt von jfa-go, angewendet",
"ombiUserDefaultsDescription": "Erstelle einen Ombi-Benutzer, konfiguriere ihn und wähle ihn dann unten aus. Seine Einstellungen/Berechtigungen werden gespeichert und auf neue Ombi-Benutzer, welche von jfa-go erstellt werden, angewendet, sofern dieses Profil ausgewählt ist.",
"userProfiles": "Benutzerprofile",
"userProfilesDescription": "Profile werden auf Benutzer angewendet, wenn sie ein Konto erstellen. Ein Profil beinhaltet Bibliothekszugriffsrechte und das Startbildschirmlayout.",
"userProfilesIsDefault": "Standard",
@@ -69,7 +69,7 @@
"preview": "Vorschau",
"reset": "Zurücksetzen",
"edit": "Bearbeiten",
"customizeMessages": "E-Mails anpassen",
"customizeMessages": "Benachrichtigungen anpassen",
"customizeMessagesDescription": "Wenn du jfa-go's E-Mail-Vorlagen nicht benutzen willst, kannst du deinen eigenen unter Verwendung von Markdown erstellen.",
"announce": "Ankündigen",
"subject": "Betreff",
@@ -100,7 +100,21 @@
"searchDiscordUser": "Gib den Discord-Benutzername ein, um den Benutzer zu finden.",
"findDiscordUser": "Suche Discord-Benutzer",
"linkMatrixDescription": "Gib den Benutzernamen und das Passwort des Benutzers ein, der als Bot verwendet werden soll. Nach dem Absenden wird die App neu gestartet.",
"matrixHomeServer": "Adresse des Homeservers"
"matrixHomeServer": "Adresse des Homeservers",
"templates": "Vorlagen",
"ombiProfile": "Ombi-Benutzerprofil",
"accessJFA": "jfa-go Zugriff",
"sendPWRValidFor": "Der Link ist 30m gültig.",
"logs": "Logdaten",
"setExpiry": "Ablauf setzen",
"sendPWRSuccess": "Link zur Passwortrücksetzung versandt.",
"sendPWRSuccessManual": "Falls der Benutzer ihn nicht erhalten hat, klicke \"Kopieren\" und sende ihm den Link manuell.",
"sendPWR": "Sende Passwortrücksetzung",
"sendPWRManual": "Benutzer {n} hat keine Kontaktmöglichkeit hinterlegt. Klicke \"Kopieren\" um einen Link zu erhalten, den du dem Benutzer manuell senden kannst.",
"accessJFASettings": "Kann nicht geändert werden, da entweder \"Nur Admin-Benutzer\" oder \"Erlaube allen Jellyfin-Nutzern sich anzumelden\" in Einstellungen > Allgemein aktiviert ist.",
"saveAsTemplate": "Als Vorlage speichern",
"deleteTemplate": "Vorlage löschen",
"templateEnterName": "Gebe einen Namen ein, um diese Vorlage zu speichern."
},
"notifications": {
"changedEmailAddress": "E-Mail-Adresse von {n} geändert.",
@@ -140,7 +154,10 @@
"noUpdatesAvailable": "Keinen neuen Aktualisierungen verfügbar.",
"updateAppliedRefresh": "Update angewendet, bitte aktualisieren.",
"telegramVerified": "Telegram-Konto verifiziert.",
"accountConnected": "Konto verbunden."
"accountConnected": "Konto verbunden.",
"savedAnnouncement": "Ankündigung gespeichert.",
"errorSetOmbiProfile": "Ombi-Profil konnte nicht gespeichert werden.",
"setOmbiProfile": "Ombi-Profil gespeichert."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -194,6 +211,10 @@
"reEnableUsers": {
"singular": "Benutzer {n} wieder aktivieren",
"plural": "Benutzer {n} wieder aktivieren"
},
"setExpiry": {
"singular": "Ablauf für {n} Benutzer setzen",
"plural": "Ablauf für {n} Benutzer setzen"
}
}
}

View File

@@ -1,5 +1,217 @@
{
"meta": {
"name": "English (GB)"
},
"quantityStrings": {
"deleteUser": {
"singular": "Delete User",
"plural": "Delete Users"
},
"deletedUser": {
"singular": "Deleted {n} user.",
"plural": "Deleted {n} users."
},
"disabledUser": {
"plural": "Disabled {n} users.",
"singular": "Disabled {n} user."
},
"extendExpiry": {
"singular": "Extend expiry for {n} user",
"plural": "Extend expiry for {n} users"
},
"extendedExpiry": {
"plural": "Extended expiry for {n} users.",
"singular": "Extended expiry for {n} user."
},
"addUser": {
"singular": "Add user",
"plural": "Add users"
},
"modifySettingsFor": {
"singular": "Modify Settings for {n} user",
"plural": "Modify Settings for {n} users"
},
"deleteNUsers": {
"plural": "Delete {n} users",
"singular": "Delete {n} user"
},
"disableUsers": {
"singular": "Disable {n} user",
"plural": "Disable {n} users"
},
"enabledUser": {
"singular": "Enabled {n} user.",
"plural": "Enabled {n} users."
},
"announceTo": {
"singular": "Announce to {n} user",
"plural": "Announce to {n} users"
},
"appliedSettings": {
"singular": "Applied settings to {n} user.",
"plural": "Applied settings to {n} users."
},
"setExpiry": {
"singular": "Set expiry for {n} user",
"plural": "Set expiry for {n} users"
},
"reEnableUsers": {
"singular": "Re-enable {n} user",
"plural": "Re-enable {n} users"
}
},
"strings": {
"invites": "Invites",
"accounts": "Accounts",
"settings": "Settings",
"inviteDays": "Days",
"inviteHours": "Hours",
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
"inviteSendToEmail": "Send to",
"login": "Login",
"logout": "Logout",
"apply": "Apply",
"delete": "Delete",
"updates": "Updates",
"expiry": "Expiry",
"variables": "Variables",
"preview": "Preview",
"markdownSupported": "Markdown is supported.",
"applyHomescreenLayout": "Apply homescreen layout",
"ombiProfile": "Ombi user profile",
"settingsApplyRestartNow": "Apply & restart",
"settingsApplied": "Settings applied.",
"userProfiles": "User Profiles",
"addProfile": "Add Profile",
"userProfilesLibraries": "Libraries",
"addProfileNameOf": "Profile Name",
"inviteDateCreated": "Created",
"settingsRestart": "Restart",
"inviteMinutes": "Minutes",
"inviteNumberOfUses": "Number of uses",
"warning": "Warning",
"create": "Create",
"name": "Name",
"conditionals": "Conditionals",
"contactThrough": "Contact through:",
"select": "Select",
"date": "Date",
"enabled": "Enabled",
"disabled": "Disabled",
"disable": "Disable",
"edit": "Edit",
"extendExpiry": "Extend expiry",
"sendPWR": "Send Password Reset",
"inviteMonths": "Months",
"inviteDuration": "Invite Duration",
"add": "Add",
"reEnable": "Re-enable",
"update": "Update",
"user": "User",
"userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
"templates": "Templates",
"accessJFA": "Access jfa-go",
"message": "Message",
"reset": "Reset",
"donate": "Donate",
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
"logs": "Logs",
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
"sendPWRSuccess": "Password reset link sent.",
"customizeMessages": "Customise Messages",
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",
"modifySettings": "Modify Settings",
"sendDeleteNotificationEmail": "Send notification message",
"sendDeleteNotifiationExample": "Your account has been deleted.",
"settingsRestarting": "Restarting…",
"settingsRestartRequired": "Restart needed",
"settingsRefreshPage": "Refresh the page in a few seconds.",
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
"settingsSave": "Save",
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.",
"addProfileDescription": "Create a Jellyfin user and configure it, then select it below. When this profile is applied to an invite, new users will be created with the settings.",
"addProfileStoreHomescreenLayout": "Store homescreen layout",
"inviteNoUsersCreated": "None yet!",
"inviteUsersCreated": "Created users",
"inviteRemainingUses": "Remaining uses",
"inviteNoInvites": "None",
"inviteExpiresInTime": "Expires in {n}",
"notifyEvent": "Notify on:",
"notifyInviteExpiry": "On expiry",
"notifyUserCreation": "On user creation",
"sendPIN": "Ask the user to send the PIN below to the bot.",
"findDiscordUser": "Find Discord user",
"linkMatrixDescription": "Enter the username and password of the user to use as a bot. Once submitted, the app will restart.",
"matrixHomeServer": "Home server address",
"saveAsTemplate": "Save as template",
"deleteTemplate": "Delete template",
"settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?",
"settingsApplyRestartLater": "Apply, restart later",
"subject": "Subject",
"setExpiry": "Set expiry",
"admin": "Admin",
"download": "Download",
"search": "Search",
"advancedSettings": "Advanced Settings",
"lastActiveTime": "Last Active",
"from": "From",
"userExpiry": "User Expiry",
"aboutProgram": "About",
"version": "Version",
"commitNoun": "Commit",
"newUser": "New User",
"profile": "Profile",
"unknown": "Unknown",
"label": "Label",
"announce": "Announce",
"sendPWRValidFor": "The link is valid for 30m.",
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.",
"userProfilesIsDefault": "Default",
"inviteNoProfile": "No Profile",
"searchDiscordUser": "Start typing the Discord username to find the user.",
"templateEnterName": "Enter a name to save this template.",
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General."
},
"notifications": {
"errorSettingsFailed": "Application failed.",
"errorLoginBlank": "The username and/or password was left blank.",
"errorLoadSettings": "Failed to load settings.",
"errorUnknown": "Unknown error.",
"errorDeleteProfile": "Failed to delete profile {n}",
"sentAnnouncement": "Announcement sent.",
"savedAnnouncement": "Announcement saved.",
"setOmbiProfile": "Stored ombi profile.",
"updateApplied": "Update applied, please restart.",
"updateAppliedRefresh": "Update applied, please refresh.",
"telegramVerified": "Telegram account verified.",
"accountConnected": "Account connected.",
"error401Unauthorized": "Unauthorised. Try refreshing the page.",
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
"errorSaveEmail": "Failed to save email.",
"errorLoadProfiles": "Failed to load profiles.",
"errorCreateProfile": "Failed to create profile {n}",
"errorLoadUsers": "Failed to load users.",
"errorSaveSettings": "Couldn't save settings.",
"errorSetOmbiProfile": "Failed to store ombi profile.",
"errorLoadOmbiUsers": "Failed to load ombi users.",
"errorFailureCheckLogs": "Failed (check console/logs)",
"errorPartialFailureCheckLogs": "Partial failure (check console/logs)",
"errorUserCreated": "Failed to create user {n}.",
"errorSendWelcomeEmail": "Failed to send welcome message (check console/logs)",
"errorApplyUpdate": "Failed to apply update, try manually.",
"errorCheckUpdate": "Failed to check for update.",
"noUpdatesAvailable": "No new updates available.",
"changedEmailAddress": "Changed email address of {n}.",
"userCreated": "User {n} created.",
"saveEmail": "Email saved.",
"createProfile": "Created profile {n}.",
"saveSettings": "Settings were saved",
"errorConnection": "Couldn't connect to jfa-go.",
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
"errorBlankFields": "Fields were left blank",
"errorSetDefaultProfile": "Failed to set default profile.",
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
"updateAvailable": "A new update is available, check settings."
}
}

View File

@@ -27,6 +27,7 @@
"enabled": "Enabled",
"disabled": "Disabled",
"reEnable": "Re-enable",
"setExpiry": "Set expiry",
"disable": "Disable",
"admin": "Admin",
"updates": "Updates",
@@ -36,6 +37,8 @@
"advancedSettings": "Advanced Settings",
"lastActiveTime": "Last Active",
"from": "From",
"after": "After",
"before": "Before",
"user": "User",
"expiry": "Expiry",
"userExpiry": "User Expiry",
@@ -47,6 +50,7 @@
"profile": "Profile",
"unknown": "Unknown",
"label": "Label",
"logs": "Logs",
"announce": "Announce",
"templates": "Templates",
"subject": "Subject",
@@ -57,8 +61,14 @@
"reset": "Reset",
"edit": "Edit",
"donate": "Donate",
"unlink": "Unlink Account",
"sendPWR": "Send Password Reset",
"contactThrough": "Contact through:",
"extendExpiry": "Extend expiry",
"sendPWRManual": "User {n} has no method of contact, press copy to get a link to send to them.",
"sendPWRSuccess": "Password reset link sent.",
"sendPWRSuccessManual": "If the user hasn't received it, press copy to get a link to manually send to them.",
"sendPWRValidFor": "The link is valid for 30m.",
"customizeMessages": "Customize Messages",
"customizeMessagesDescription": "If you don't want to use jfa-go's message templates, you can create your own using Markdown.",
"markdownSupported": "Markdown is supported.",
@@ -77,8 +87,8 @@
"settingsRefreshPage": "Refresh the page in a few seconds.",
"settingsRequiredOrRestartMessage": "Note: {n} indicates a required field, {n} indicates changes require a restart.",
"settingsSave": "Save",
"ombiUserDefaults": "Ombi user defaults",
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go",
"ombiProfile": "Ombi user profile",
"ombiUserDefaultsDescription": "Create an Ombi user and configure it, then select it below. It's settings/permissions will be stored and applied to new Ombi users created by jfa-go when this profile is selected.",
"userProfiles": "User Profiles",
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile include library access rights and homescreen layout.",
"userProfilesIsDefault": "Default",
@@ -104,7 +114,17 @@
"matrixHomeServer": "Home server address",
"saveAsTemplate": "Save as template",
"deleteTemplate": "Delete template",
"templateEnterName": "Enter a name to save this template."
"templateEnterName": "Enter a name to save this template.",
"accessJFA": "Access jfa-go",
"accessJFASettings": "Cannot be changed as either \"Admin Only\" or \"Allow All\" has been set in Settings > General.",
"sortingBy": "Sorting By",
"filters": "Filters",
"clickToRemoveFilter": "Click to remove this filter.",
"clearSearch": "Clear search",
"actions": "Actions",
"searchOptions": "Search Options",
"matchText": "Match Text",
"jellyfinID": "Jellyfin ID"
},
"notifications": {
"changedEmailAddress": "Changed email address of {n}.",
@@ -114,7 +134,7 @@
"saveEmail": "Email saved.",
"sentAnnouncement": "Announcement sent.",
"savedAnnouncement": "Announcement saved.",
"setOmbiDefaults": "Stored ombi defaults.",
"setOmbiProfile": "Stored ombi profile.",
"updateApplied": "Update applied, please restart.",
"updateAppliedRefresh": "Update applied, please refresh.",
"telegramVerified": "Telegram account verified.",
@@ -135,7 +155,7 @@
"errorLoadUsers": "Failed to load users.",
"errorSaveSettings": "Couldn't save settings.",
"errorLoadSettings": "Failed to load settings.",
"errorSetOmbiDefaults": "Failed to store ombi defaults.",
"errorSetOmbiProfile": "Failed to store ombi profile.",
"errorLoadOmbiUsers": "Failed to load ombi users.",
"errorChangedEmailAddress": "Couldn't change email address of {n}.",
"errorFailureCheckLogs": "Failed (check console/logs)",
@@ -196,6 +216,10 @@
"singular": "Extend expiry for {n} user",
"plural": "Extend expiry for {n} users"
},
"setExpiry": {
"singular": "Set expiry for {n} user",
"plural": "Set expiry for {n} users"
},
"extendedExpiry": {
"singular": "Extended expiry for {n} user.",
"plural": "Extended expiry for {n} users."

View File

@@ -1,6 +1,6 @@
{
"meta": {
"name": "Español(ES)"
"name": "Español (ES)"
},
"strings": {
"invites": "Invitaciones",
@@ -13,11 +13,11 @@
"inviteNumberOfUses": "Números de usos",
"inviteDuration": "Duración de invitación",
"warning": "Advertencia",
"inviteInfiniteUsesWarning": "Las invitaciones con usos infinitos pueden usarse abusivamente",
"inviteInfiniteUsesWarning": "Las invitaciones con usos infinitos se pueden usar de forma abusiva",
"inviteSendToEmail": "Enviar a",
"login": "Acceso",
"logout": "Cerrar sesión",
"create": "Cerrar sesión",
"create": "Crear",
"apply": "Aplicar",
"delete": "Eliminar",
"name": "Nombre",
@@ -37,10 +37,10 @@
"user": "Usuario",
"expiry": "Expiración",
"userExpiry": "Caducidad del usuario",
"userExpiryDescription": "Una cantidad específica de tiempo después de cada registro, jfa-go eliminará/deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.",
"userExpiryDescription": "Una cantidad de tiempo específica después de cada registro, jfa-go eliminará / deshabilitará la cuenta. Puede cambiar este comportamiento en la configuración.",
"aboutProgram": "Acerca de",
"version": "Versión",
"commitNoun": "Cometer",
"commitNoun": "Revisión",
"newUser": "Nuevo usuario",
"profile": "Perfil",
"unknown": "Desconocido",
@@ -49,38 +49,38 @@
"subject": "Asunto del email",
"message": "Mensaje",
"variables": "Variables",
"preview": "Previsualizar",
"preview": "Vista previa",
"reset": "Reiniciar",
"edit": "Editar",
"extendExpiry": "Extender el vencimiento",
"customizeMessages": "Personalizar emails",
"customizeMessagesDescription": "Si no desea utilizar las plantillas de correo electrónico de jfa-go, puede crear las suyas propias con Markdown.",
"customizeMessages": "Personalizar mensajes",
"customizeMessagesDescription": "Si no desea utilizar las plantillas de mensajes de jfa-go, puede crear las suyas con Markdown.",
"markdownSupported": "Se admite Markdown.",
"modifySettings": "Modificar configuración",
"modifySettingsDescription": "Aplique la configuración de un perfil existente u obténgalos directamente de un usuario.",
"applyHomescreenLayout": "Aplicar el diseño de la pantalla de inicio",
"sendDeleteNotificationEmail": "Enviar notificación a correo",
"sendDeleteNotificationEmail": "Enviar mensaje de notificación",
"sendDeleteNotifiationExample": "Tu cuenta ha sido eliminada.",
"settingsRestart": "Reiniciar",
"settingsRestarting": "Reiniciando…",
"settingsRestartRequired": "Reinicio necesario",
"settingsRestartRequiredDescription": "Es necesario reiniciar para aplicar algunas configuraciones que cambió. ¿Reiniciar ahora o más tarde?",
"settingsApplyRestartLater": "Aplicar, reiniciar más tarde",
"settingsApplyRestartNow": "Aplicar, reiniciar más tarde",
"settingsApplyRestartNow": "Aplicar y reiniciar",
"settingsApplied": "Se aplicó la configuración.",
"settingsRefreshPage": "Actualiza la página en unos segundos.",
"settingsRequiredOrRestartMessage": "Nota: {n} indica un campo obligatorio, {n} indica que los cambios requieren un reinicio.",
"settingsSave": "Guardar",
"ombiUserDefaults": "Valores predeterminados de usuario de Ombi",
"ombiUserDefaultsDescription": "Cree un usuario Ombi y configúrelo, luego selecciónelo a continuación. Sus configuraciones / permisos se almacenarán y aplicarán a los nuevos usuarios de Ombi creados por jfa-go",
"ombiUserDefaultsDescription": "Cree un usuario Ombi y configúrelo, luego selecciónelo a continuación. Sus configuraciones/permisos se almacenarán y aplicarán a los nuevos usuarios de Ombi creados por jfa-go cuando se seleccione este perfil.",
"userProfiles": "Perfiles de usuario",
"userProfilesDescription": "Los perfiles se aplican a los usuarios cuando crean una cuenta. Un perfil incluye los derechos de acceso a la biblioteca y el diseño de la pantalla de inicio.",
"userProfilesIsDefault": "Defecto",
"userProfilesIsDefault": "Por defecto",
"userProfilesLibraries": "Bibliotecas",
"addProfile": "Agregar perfil",
"addProfile": "Agregar Perfil",
"addProfileDescription": "Cree un usuario de Jellyfin y configúrelo, luego selecciónelo a continuación. Cuando este perfil se aplica a una invitación, se crearán nuevos usuarios con la configuración.",
"addProfileNameOf": "Nombre de perfil",
"addProfileStoreHomescreenLayout": "Diseño de la pantalla de inicio de la tienda",
"addProfileStoreHomescreenLayout": "Guardar el diseño de la pantalla de inicio",
"inviteNoUsersCreated": "¡Ninguno todavía!",
"inviteUsersCreated": "Usuarios creados",
"inviteNoProfile": "Sin perfil",
@@ -92,7 +92,29 @@
"notifyInviteExpiry": "Al vencimiento",
"notifyUserCreation": "Sobre la creación de usuarios",
"conditionals": "Condicionales",
"donate": "Donar"
"donate": "Donar",
"add": "Agregar",
"templates": "Plantillas",
"contactThrough": "Contactar a través de:",
"select": "Seleccionar",
"sendPIN": "Pídale al usuario que envíe el PIN a continuación al bot.",
"searchDiscordUser": "Comienza a escribir el nombre de usuario de Discord para encontrar al usuario.",
"findDiscordUser": "Encontrar usuario de Discord",
"linkMatrixDescription": "Ingrese el nombre de usuario y la contraseña del usuario para usar como bot. Una vez enviada, la aplicación se reiniciará.",
"matrixHomeServer": "Dirección del servidor de inicio",
"saveAsTemplate": "Guardar como plantilla",
"deleteTemplate": "Eliminar plantilla",
"templateEnterName": "Ingrese un nombre para guardar esta plantilla.",
"setExpiry": "Establecer vencimiento",
"sendPWR": "Enviar restablecimiento de contraseña",
"sendPWRSuccess": "Se envió el enlace para restablecer la contraseña.",
"sendPWRSuccessManual": "Si el usuario no lo ha recibido, presione copiar para generar el enlace y enviárselo manualmente.",
"sendPWRValidFor": "El enlace es válido por 30m.",
"sendPWRManual": "El usuario {n} no tiene ningún método de contacto, presione copiar para generar el enlace para enviarle.",
"ombiProfile": "Perfil de usuario de Ombi",
"logs": "Registros",
"accessJFA": "Acceso",
"accessJFASettings": "No se puede cambia, ya que se ha establecido \"Solo administradores\" o \"Permitir a todos\" en Configuración > General."
},
"notifications": {
"changedEmailAddress": "Se cambió la dirección de correo electrónico de {n}.",
@@ -106,7 +128,7 @@
"errorConnection": "No se pudo conectar a jfa-go.",
"error401Unauthorized": "No autorizado. Intente actualizar la página.",
"errorSettingsAppliedNoHomescreenLayout": "Se aplicó la configuración, pero es posible que no se haya aplicado el diseño de la pantalla de inicio.",
"errorHomescreenAppliedNoSettings": "Se aplicó el diseño de la pantalla de inicio, pero es posible que no se haya aplicado la configuración.",
"errorHomescreenAppliedNoSettings": "Se aplicó el diseño de la pantalla de inicio, pero es posible que la aplicación de la configuración haya fallado.",
"errorSettingsFailed": "La aplicación falló.",
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
"errorUnknown": "Error desconocido.",
@@ -120,16 +142,22 @@
"errorSaveSettings": "No se pudo guardar la configuración.",
"errorLoadSettings": "No se pudo cargar la configuración.",
"errorSetOmbiDefaults": "No se pudieron almacenar los valores predeterminados de ombi.",
"errorLoadOmbiUsers": "No se pudieron cargar los usuarios de ombi.",
"errorLoadOmbiUsers": "No se pudieron cargar los usuarios de Ombi.",
"errorChangedEmailAddress": "No se pudo cambiar la dirección de correo electrónico de {n}.",
"errorFailureCheckLogs": "Fallido (ver consola / registros)",
"errorPartialFailureCheckLogs": "Fallo parcial (ver consola / registros)",
"errorFailureCheckLogs": "Fallido (ver consola/registros)",
"errorPartialFailureCheckLogs": "Fallo parcial (ver consola/registros)",
"errorUserCreated": "No se pudo crear el usuario {n}.",
"errorSendWelcomeEmail": "No se pudo enviar el correo electrónico de bienvenida (verifique la consola / registros)",
"errorSendWelcomeEmail": "No se pudo enviar el mensaje de bienvenida (verifique la consola/registros)",
"errorApplyUpdate": "No se pudo aplicar la actualización, intente manualmente.",
"errorCheckUpdate": "No se pudo comprobar la actualización.",
"updateAvailable": "Hay una nueva actualización disponible, verifique la configuración.",
"noUpdatesAvailable": "No hay nuevas actualizaciones disponibles."
"noUpdatesAvailable": "No hay nuevas actualizaciones disponibles.",
"updateAppliedRefresh": "Actualización aplicada, por favor actualice.",
"accountConnected": "Cuenta vinculada.",
"savedAnnouncement": "Anuncio guardado.",
"telegramVerified": "Cuenta de Telegram verificada.",
"setOmbiProfile": "Perfil de Ombi guardado.",
"errorSetOmbiProfile": "No se pudo guardar el perfil de Ombi."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -141,8 +169,8 @@
"plural": "Eliminar {n} usuarios"
},
"disableUsers": {
"singular": "Deshabilitar {n} usuario",
"plural": "Inhabilitar {n} usuarios"
"singular": "Desactivar {n} usuario",
"plural": "Desactivar {n} usuarios"
},
"reEnableUsers": {
"singular": "Reactivar {n} usuario",
@@ -157,32 +185,36 @@
"plural": "Borrar usuarios"
},
"deletedUser": {
"singular": "Usuario eliminado {n}.",
"plural": "Usuarios eliminados {n}."
"singular": "{n} usuario eliminado.",
"plural": "{n} usuarios eliminados."
},
"disabledUser": {
"singular": "Usuario deshabilitado {n}.",
"plural": "Usuarios deshabilitados {n}."
"singular": "{n} usuario desactivado.",
"plural": "{n} usuarios desactivados."
},
"enabledUser": {
"singular": "Usuario {n} habilitado.",
"plural": "Usuarios {n} habilitados."
"singular": "{n} usuario activado.",
"plural": "{n} usuarios activados."
},
"announceTo": {
"singular": "Anunciar al usuario {n}",
"plural": "Anunciar a los usuarios {n}"
"singular": "Anunciar a {n} usuario",
"plural": "Anunciar a {n} usuarios"
},
"appliedSettings": {
"singular": "Se aplicó la configuración al usuario {n}.",
"plural": "Se aplicó la configuración a los usuarios {n}."
"singular": "Se aplicó la configuración a {n} usuario.",
"plural": "Se aplicó la configuración a {n} usuarios."
},
"extendExpiry": {
"singular": "Extender la expiración para el usuario {n}",
"plural": "Extender la expiración para los usuarios {n}"
"singular": "Extender la expiración para {n} usuario",
"plural": "Extender la expiración para {n} usuarios"
},
"extendedExpiry": {
"singular": "Caducidad extendida para el usuario {n}.",
"plural": "Caducidad extendida para los usuarios {n}."
"singular": "Caducidad extendida para {n} usuario.",
"plural": "Caducidad extendida para {n} usuarios."
},
"setExpiry": {
"singular": "Fijar la caducidad del usuario {n}",
"plural": "Establecer la caducidad para {n} usuarios"
}
}
}

View File

@@ -45,7 +45,7 @@
"settingsRequiredOrRestartMessage": "Remarque: {n} indique un champ obligatoire, {n} indique que les modifications nécessitent un redémarrage.",
"settingsSave": "Sauver",
"ombiUserDefaults": "Paramètres par défaut de l'utilisateur Ombi",
"ombiUserDefaultsDescription": "Créez un utilisateur Ombi et configurez-le, puis sélectionnez-le ci-dessous. Ses paramètres/autorisations seront stockés et appliqués aux nouveaux utilisateurs Ombi créés par jfa-go",
"ombiUserDefaultsDescription": "Créez un utilisateur Ombi et configurez-le, puis sélectionnez-le ci-dessous. Ses paramètres/autorisations seront stockés et appliqués aux nouveaux utilisateurs Ombi créés par jfa-go lorsque ce profil est sélectionné.",
"userProfiles": "Profils d'utilisateurs",
"userProfilesDescription": "Les profils sont appliqués aux utilisateurs lorsqu'ils créent un compte. Un profil inclut les droits d'accès à la bibliothèque et la disposition de l'écran d'accueil.",
"userProfilesIsDefault": "Défaut",
@@ -55,7 +55,7 @@
"addProfileNameOf": "Nom de profil",
"addProfileStoreHomescreenLayout": "Enregistrer la disposition de l'écran d'accueil",
"inviteNoUsersCreated": "Aucun pour l'instant !",
"inviteUsersCreated": "Utilisateurs créer",
"inviteUsersCreated": "Utilisateurs créés",
"inviteNoProfile": "Aucun profil",
"inviteDateCreated": "Créer",
"inviteRemainingUses": "Utilisations restantes",
@@ -64,7 +64,7 @@
"notifyEvent": "Notifier sur :",
"notifyInviteExpiry": "À l'expiration",
"notifyUserCreation": "à la création de l'utilisateur",
"label": "Etiquette",
"label": "Nom",
"settingsRestarting": "Redémarrage…",
"settingsRestart": "Redémarrer",
"announce": "Annoncer",
@@ -101,7 +101,21 @@
"findDiscordUser": "Trouver l'utilisateur Discord",
"linkMatrixDescription": "Entrez le nom d'utilisateur et le mot de passe de l'utilisateur pour lutilisateur comme bot. Une fois soumis, l'application va redémarrer.",
"searchDiscordUser": "Commencez à taper le nom d'utilisateur Discord pour trouver l'utilisateur.",
"matrixHomeServer": "Adresse du serveur"
"matrixHomeServer": "Adresse du serveur domestique",
"saveAsTemplate": "Sauvegarder comme modèle",
"templateEnterName": "Entrez un nom pour sauvegarder ce modèle.",
"deleteTemplate": "Supprimer le modèle",
"templates": "Modèles",
"setExpiry": "Paramétrer l'expiration",
"sendPWRSuccess": "Réinitialisation du mot de passe envoyée.",
"sendPWR": "Envoyer une réinitialisation du mot de passe",
"sendPWRValidFor": "Ce lien est valable 30min.",
"sendPWRManual": "L'utilisateur {n} n'a pas indiqué de méthode de contact, appuyez sur copier pour recevoir un lien à lui envoyer.",
"sendPWRSuccessManual": "Si l'utilisateur ne l'a pas reçu, appuyez sur copier pour recevoir un lien à lui envoyer manuellement.",
"ombiProfile": "Profil d'utilisateur Ombi",
"logs": "Logs",
"accessJFA": "Accès à jfa-go",
"accessJFASettings": "Ne peut pas être changé car \"Admin Only\" ou \"Allow All\" a été défini dans Paramètres > Général."
},
"notifications": {
"changedEmailAddress": "Adresse e-mail modifiée de {n}.",
@@ -141,7 +155,10 @@
"noUpdatesAvailable": "Aucune nouvelle mise à jour disponible.",
"telegramVerified": "Compte Telegram vérifié.",
"updateAppliedRefresh": "Mise à jour appliquée, veuillez actualiser.",
"accountConnected": "Compte connecté."
"accountConnected": "Compte connecté.",
"savedAnnouncement": "Annonce enregistrée.",
"setOmbiProfile": "Profil ombi enregistré.",
"errorSetOmbiProfile": "Echec de la sauvegarde du profil ombi."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -195,6 +212,10 @@
"disabledUser": {
"singular": "{n} utilisateur désactivé.",
"plural": "{n} utilisateurs désactivés."
},
"setExpiry": {
"singular": "Définir l'expiration pour {n} utilisateur",
"plural": "Définir l'expiration pour {n} utilisateurs"
}
}
}

211
lang/admin/hu-hu.json Normal file
View File

@@ -0,0 +1,211 @@
{
"meta": {
"name": "Magyar (HU)"
},
"strings": {
"invites": "Meghívások",
"accounts": "Fiókok",
"settings": "Beállítások",
"inviteMonths": "Hónapok",
"inviteDays": "Napok",
"inviteHours": "Órák",
"inviteMinutes": "Percek",
"inviteNumberOfUses": "Felhasználások száma",
"inviteDuration": "Meghívás időtartama",
"warning": "Figyelmeztetés",
"inviteInfiniteUsesWarning": "a végtelen felhasználású meghívókkal visszaélhetnek",
"inviteSendToEmail": "Címzett",
"login": "Belépés",
"logout": "Kijelentkezés",
"create": "Létrehozás",
"apply": "Alkalmaz",
"delete": "Törlés",
"add": "Hozzáadás",
"select": "Kiválasztás",
"name": "Név",
"date": "Dátum",
"enabled": "Engedélyezve",
"disabled": "Tiltva",
"reEnable": "Újra engedélyezés",
"setExpiry": "Lejárat beállítása",
"disable": "Letiltás",
"admin": "Adminisztrátor",
"updates": "Frissítések",
"update": "Frissítés",
"download": "Letöltés",
"search": "Keresés",
"advancedSettings": "További beállítások",
"lastActiveTime": "Utoljára aktív",
"from": "Feladó",
"user": "Felhasználó",
"expiry": "Lejárat",
"userExpiry": "Felhasználói lejárat",
"userExpiryDescription": "Egy meghatározott idő után minden regisztrációt töröl, vagy felfüggeszt a jfa-go. Ezt a működést megváltoztathatod a beállításokban.",
"aboutProgram": "Névjegy",
"version": "Verzió",
"commitNoun": "Elkövet",
"newUser": "Új felhasználó",
"profile": "Profil",
"unknown": "Ismeretlen",
"label": "Címke",
"logs": "Naplók",
"announce": "Bejelentés",
"templates": "Sablonok",
"subject": "Téma",
"message": "Üzenet",
"variables": "Változók",
"conditionals": "Feltételek",
"preview": "Előnézet",
"reset": "Visszaállítás",
"edit": "Szerkesztés",
"donate": "Támogatás",
"sendPWR": "Jelszó visszaállítás küldése",
"contactThrough": "",
"extendExpiry": "",
"sendPWRManual": "",
"sendPWRSuccess": "",
"sendPWRSuccessManual": "",
"sendPWRValidFor": "",
"customizeMessages": "",
"customizeMessagesDescription": "",
"markdownSupported": "",
"modifySettings": "",
"modifySettingsDescription": "",
"applyHomescreenLayout": "",
"sendDeleteNotificationEmail": "",
"sendDeleteNotifiationExample": "",
"settingsRestart": "",
"settingsRestarting": "",
"settingsRestartRequired": "",
"settingsRestartRequiredDescription": "",
"settingsApplyRestartLater": "",
"settingsApplyRestartNow": "",
"settingsApplied": "",
"settingsRefreshPage": "",
"settingsRequiredOrRestartMessage": "",
"settingsSave": "",
"ombiProfile": "",
"ombiUserDefaultsDescription": "",
"userProfiles": "",
"userProfilesDescription": "",
"userProfilesIsDefault": "",
"userProfilesLibraries": "",
"addProfile": "",
"addProfileDescription": "",
"addProfileNameOf": "",
"addProfileStoreHomescreenLayout": "",
"inviteNoUsersCreated": "",
"inviteUsersCreated": "",
"inviteNoProfile": "",
"inviteDateCreated": "",
"inviteRemainingUses": "",
"inviteNoInvites": "",
"inviteExpiresInTime": "",
"notifyEvent": "",
"notifyInviteExpiry": "",
"notifyUserCreation": "",
"sendPIN": "",
"searchDiscordUser": "",
"findDiscordUser": "",
"linkMatrixDescription": "",
"matrixHomeServer": "",
"saveAsTemplate": "",
"deleteTemplate": "",
"templateEnterName": ""
},
"notifications": {
"changedEmailAddress": "",
"userCreated": "",
"createProfile": "",
"saveSettings": "",
"saveEmail": "",
"sentAnnouncement": "",
"savedAnnouncement": "",
"setOmbiProfile": "",
"updateApplied": "",
"updateAppliedRefresh": "",
"telegramVerified": "",
"accountConnected": "",
"errorConnection": "",
"error401Unauthorized": "",
"errorSettingsAppliedNoHomescreenLayout": "",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorLoginBlank": "",
"errorUnknown": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"errorSaveSettings": "",
"errorLoadSettings": "",
"errorSetOmbiProfile": "",
"errorLoadOmbiUsers": "",
"errorChangedEmailAddress": "",
"errorFailureCheckLogs": "",
"errorPartialFailureCheckLogs": "",
"errorUserCreated": "",
"errorSendWelcomeEmail": "",
"errorApplyUpdate": "",
"errorCheckUpdate": "",
"updateAvailable": "",
"noUpdatesAvailable": ""
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "",
"plural": ""
},
"deleteNUsers": {
"singular": "",
"plural": ""
},
"disableUsers": {
"singular": "",
"plural": ""
},
"reEnableUsers": {
"singular": "",
"plural": ""
},
"addUser": {
"singular": "",
"plural": ""
},
"deleteUser": {
"singular": "",
"plural": ""
},
"deletedUser": {
"singular": "",
"plural": ""
},
"disabledUser": {
"singular": "",
"plural": ""
},
"enabledUser": {
"singular": "",
"plural": ""
},
"announceTo": {
"singular": "",
"plural": ""
},
"appliedSettings": {
"singular": "",
"plural": ""
},
"extendExpiry": {
"singular": "",
"plural": ""
},
"extendedExpiry": {
"singular": "",
"plural": ""
}
}
}

View File

@@ -43,7 +43,7 @@
"settingsRequiredOrRestartMessage": "Opmerking: {n} is een verplicht veld, {n} geeft aan dat na wijzigen een herstart nodig is.",
"settingsSave": "Opslaan",
"ombiUserDefaults": "Ombi gebruiker standaardinstellingen",
"ombiUserDefaultsDescription": "Maak een Ombi gebruiker aan met de gewenste instellingen, en selecteer deze hieronder. Deze instellingen/rechten worden opgeslagen en toegepast voor nieuwe Ombi gebruikers die jfa-go aanmaakt",
"ombiUserDefaultsDescription": "Maak een Ombi gebruiker aan met de gewenste instellingen, en selecteer deze hieronder. Deze instellingen/rechten worden opgeslagen en toegepast voor nieuwe Ombi gebruikers die jfa-go aanmaakt als dit profiel is geselecteerd.",
"userProfiles": "Gebruikersprofielen",
"userProfilesDescription": "Profielen worden toegepast op gebruikers wanneer ze een account aanmaken. Een profiel bevat rechten voor bibliotheken en indeling van de startpagina.",
"userProfilesIsDefault": "Standaard",
@@ -104,7 +104,17 @@
"templates": "Sjablonen",
"templateEnterName": "Voer een naam in om dit sjabloon op te slaan.",
"saveAsTemplate": "Sla op als sjabloon",
"deleteTemplate": "Verwijder sjabloon"
"deleteTemplate": "Verwijder sjabloon",
"setExpiry": "Stel verloop in",
"sendPWRManual": "Gebruiker {n} heeft geen contactmogelijkheden, druk op kopiëren om een link te krijgen die je kunt sturen.",
"sendPWRSuccessManual": "Als de gebruiker hem niet heeft ontvangen, druk dan op kopiëren om een link te krijgen die je handmatig kunt sturen.",
"sendPWR": "Verstuur wachtwoordreset",
"sendPWRSuccess": "Wachtwoordreset-link verstuurd.",
"sendPWRValidFor": "De link is 30m geldig.",
"ombiProfile": "Ombi gebruikersprofiel",
"logs": "Logs",
"accessJFA": "Toegang tot jfa-go",
"accessJFASettings": "Kan niet worden aangepast, omdat \"Alleen beheerders\" of \"Laat alle Jellyfin-gebruikers inloggen\" is aangevinkt in Instellingen > Algemeen."
},
"notifications": {
"changedEmailAddress": "E-mailadres van {n} gewijzigd.",
@@ -145,7 +155,9 @@
"telegramVerified": "Telegram-account goedgekeurd.",
"updateAppliedRefresh": "Update toegepast, ververs alsjeblieft.",
"accountConnected": "Account gekoppeld.",
"savedAnnouncement": "Aankondiging opgeslagen."
"savedAnnouncement": "Aankondiging opgeslagen.",
"setOmbiProfile": "Opgeslagen ombi-profiel.",
"errorSetOmbiProfile": "Opslaan van ombi-profiel mislukt."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -199,6 +211,10 @@
"enabledUser": {
"singular": "{n} gebruiker ingeschakeld.",
"plural": "{n} gebruikers ingeschakeld."
},
"setExpiry": {
"singular": "Stel verloop in voor {n} gebruiker",
"plural": "Stel verloop in voor {n} gebruikers"
}
}
}

217
lang/admin/pl-PL.json Normal file
View File

@@ -0,0 +1,217 @@
{
"meta": {
"name": "Polski (PL)"
},
"strings": {
"invites": "Zaproszenia",
"accounts": "Konta",
"settings": "Ustawienia",
"inviteMonths": "Miesiące",
"inviteDays": "Dni",
"inviteHours": "Godziny",
"inviteMinutes": "Minuty",
"inviteNumberOfUses": "Liczba użyć",
"inviteDuration": "Czas trwania zaproszenia",
"warning": "Ostrzeżenie",
"inviteInfiniteUsesWarning": "",
"inviteSendToEmail": "",
"login": "",
"logout": "",
"create": "",
"apply": "",
"delete": "",
"add": "",
"select": "",
"name": "Imię",
"date": "Data",
"enabled": "Włączone",
"disabled": "Wyłączone",
"reEnable": "",
"setExpiry": "",
"disable": "Wyłączone",
"admin": "Admin",
"updates": "Aktualizacje",
"update": "Aktualizacja",
"download": "Pobierz",
"search": "Szukaj",
"advancedSettings": "Zaawansowane",
"lastActiveTime": "Ostatnia aktywność",
"from": "Od",
"user": "Użytkownik",
"expiry": "Wygasa",
"userExpiry": "Użytkownik wygasa",
"userExpiryDescription": "",
"aboutProgram": "O",
"version": "Wersja",
"commitNoun": "",
"newUser": "",
"profile": "",
"unknown": "",
"label": "",
"logs": "",
"announce": "",
"templates": "",
"subject": "",
"message": "Wiadomość",
"variables": "",
"conditionals": "",
"preview": "",
"reset": "Zresetuj",
"edit": "Edytuj",
"donate": "",
"sendPWR": "",
"contactThrough": "",
"extendExpiry": "",
"sendPWRManual": "",
"sendPWRSuccess": "",
"sendPWRSuccessManual": "",
"sendPWRValidFor": "",
"customizeMessages": "",
"customizeMessagesDescription": "",
"markdownSupported": "",
"modifySettings": "Zmień ustawienia",
"modifySettingsDescription": "",
"applyHomescreenLayout": "",
"sendDeleteNotificationEmail": "",
"sendDeleteNotifiationExample": "",
"settingsRestart": "",
"settingsRestarting": "",
"settingsRestartRequired": "",
"settingsRestartRequiredDescription": "",
"settingsApplyRestartLater": "",
"settingsApplyRestartNow": "",
"settingsApplied": "",
"settingsRefreshPage": "",
"settingsRequiredOrRestartMessage": "",
"settingsSave": "",
"ombiProfile": "",
"ombiUserDefaultsDescription": "",
"userProfiles": "",
"userProfilesDescription": "",
"userProfilesIsDefault": "",
"userProfilesLibraries": "",
"addProfile": "Dodaj Profil",
"addProfileDescription": "",
"addProfileNameOf": "Nazwa profilu",
"addProfileStoreHomescreenLayout": "",
"inviteNoUsersCreated": "",
"inviteUsersCreated": "",
"inviteNoProfile": "",
"inviteDateCreated": "Utworzone",
"inviteRemainingUses": "",
"inviteNoInvites": "",
"inviteExpiresInTime": "",
"notifyEvent": "",
"notifyInviteExpiry": "",
"notifyUserCreation": "",
"sendPIN": "Poproś użytkownika aby wysłał kod PIN przy użyciu bota.",
"searchDiscordUser": "",
"findDiscordUser": "",
"linkMatrixDescription": "",
"matrixHomeServer": "",
"saveAsTemplate": "",
"deleteTemplate": "Usuń szablon",
"templateEnterName": "Wprowadź nazwę aby zapisać szablon.",
"accessJFA": "",
"accessJFASettings": ""
},
"notifications": {
"changedEmailAddress": "Zmieniono adres email {n}.",
"userCreated": "Użytkownik {n} utworzony.",
"createProfile": "Stworzono profil {n}.",
"saveSettings": "Ustawienia zostały zapisane",
"saveEmail": "Email zapisany.",
"sentAnnouncement": "Ogłoszenie wysłane.",
"savedAnnouncement": "Ogłoszenie zostało zapisane.",
"setOmbiProfile": "Zapisany profil ombi.",
"updateApplied": "Aktualizacja zastosowana, uruchom ponownie.",
"updateAppliedRefresh": "Aktualizacja zastosowana, odśwież.",
"telegramVerified": "Konto telegramu zweryfikowane.",
"accountConnected": "Konto połączone.",
"errorConnection": "Nie udało się połączyć z jfa-go.",
"error401Unauthorized": "Nieautoryzowany. Spróbuj odświeżyć stronę.",
"errorSettingsAppliedNoHomescreenLayout": "Zastosowano ustawienia, ale zastosowanie układu ekranu głównego mogło się nie powieść.",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorLoginBlank": "",
"errorUnknown": "Nieznany błąd.",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"errorSaveSettings": "",
"errorLoadSettings": "",
"errorSetOmbiProfile": "",
"errorLoadOmbiUsers": "",
"errorChangedEmailAddress": "",
"errorFailureCheckLogs": "",
"errorPartialFailureCheckLogs": "",
"errorUserCreated": "",
"errorSendWelcomeEmail": "",
"errorApplyUpdate": "",
"errorCheckUpdate": "",
"updateAvailable": "",
"noUpdatesAvailable": ""
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "",
"plural": ""
},
"deleteNUsers": {
"singular": "",
"plural": ""
},
"disableUsers": {
"singular": "",
"plural": ""
},
"reEnableUsers": {
"singular": "",
"plural": ""
},
"addUser": {
"singular": "",
"plural": ""
},
"deleteUser": {
"singular": "",
"plural": ""
},
"deletedUser": {
"singular": "",
"plural": ""
},
"disabledUser": {
"singular": "",
"plural": ""
},
"enabledUser": {
"singular": "",
"plural": ""
},
"announceTo": {
"singular": "",
"plural": ""
},
"appliedSettings": {
"singular": "",
"plural": ""
},
"extendExpiry": {
"singular": "",
"plural": ""
},
"setExpiry": {
"singular": "",
"plural": ""
},
"extendedExpiry": {
"singular": "",
"plural": ""
}
}
}

View File

@@ -44,7 +44,7 @@
"settingsRequiredOrRestartMessage": "Nota: {n} indica campo obrigatório, {n} indica que as alterações requer um reinício.",
"settingsSave": "Salve",
"ombiUserDefaults": "Padrões do usuário Ombi",
"ombiUserDefaultsDescription": "Crie um usuário Ombi, configure-o e selecione-o abaixo. Suas configurações/permissões serão armazenadas e aplicadas aos novos usuários Ombi criados pelo jfa-go",
"ombiUserDefaultsDescription": "Crie um usuário Ombi e configure-o, depois selecione-o abaixo. Suas configurações/permissões serão armazenadas e aplicadas a novos usuários Ombi criados pelo jfa-go quando este perfil for selecionado.",
"userProfiles": "Perfil de usuário",
"userProfilesDescription": "Os perfis são aplicados aos usuários quando eles criam uma conta. Um perfil inclui direitos de acesso à biblioteca e layout da tela inicial.",
"userProfilesIsDefault": "Padrão",
@@ -72,7 +72,7 @@
"customizeMessagesDescription": "Se não quiser usar os modelos de email do jfa-go, você pode criar o seu próprio usando o Markdown.",
"variables": "Variáveis",
"preview": "Pre-visualizar",
"reset": "Reiniciar",
"reset": "Redefinir",
"edit": "Editar",
"customizeMessages": "Customizar Emails",
"disabled": "Desativado",
@@ -94,7 +94,7 @@
"conditionals": "Condicionais",
"donate": "Doar",
"contactThrough": "Contato através:",
"sendPIN": "Peça ao usuário para enviar o PIN abaixo para o bot.",
"sendPIN": "Peça que o usuário envie o PIN abaixo para o bot.",
"searchDiscordUser": "Digite o nome de usuário do Discord.",
"findDiscordUser": "Encontrar usuário Discord",
"add": "Adicionar",
@@ -104,7 +104,17 @@
"matrixHomeServer": "Endereço do servidor local",
"saveAsTemplate": "Salvar o modelo",
"deleteTemplate": "Deletar modelo",
"templateEnterName": "Digite um nome para salvar este modelo."
"templateEnterName": "Digite um nome para salvar este modelo.",
"ombiProfile": "Ombi perfil de usuário",
"setExpiry": "Definir vencimento",
"logs": "Histórico",
"sendPWRManual": "O usuário {a} não tem método de contato, pressione copiar para obter um link para enviar a ele.",
"accessJFA": "Acessar o jfa-go",
"sendPWR": "Enviar redefinição de senha",
"sendPWRSuccess": "Link de redefinição de senha enviado.",
"sendPWRSuccessManual": "Se o usuário não o recebeu, pressione copiar para obter um link para enviar manualmente a ele.",
"sendPWRValidFor": "O link é válido por 30m.",
"accessJFASettings": "Não pode ser alterado porque \"Só Administrador\" ou \"Permitir todos\" foi definido em Configurações> Geral."
},
"notifications": {
"changedEmailAddress": "Endereço de e-mail alterado de {n}.",
@@ -145,7 +155,9 @@
"telegramVerified": "Conta do Telegram verificada.",
"updateAppliedRefresh": "Atualização instalada, atualize.",
"accountConnected": "Conta conectada.",
"savedAnnouncement": "Anúncio salvo."
"savedAnnouncement": "Anúncio salvo.",
"setOmbiProfile": "Perfil ombi armazenado.",
"errorSetOmbiProfile": "Falha ao armazenar o perfil ombi."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -199,6 +211,10 @@
"enabledUser": {
"singular": "{n} Usuário habilitado.",
"plural": "{n} Usuários habilitado."
},
"setExpiry": {
"singular": "Definir expiração para {a} usuário",
"plural": "Definir expiração para {a} usuários"
}
}
}

212
lang/admin/vi-vn.json Normal file
View File

@@ -0,0 +1,212 @@
{
"meta": {
"name": "Tiếng Anh (Mỹ)"
},
"strings": {
"invites": "Lời mời",
"accounts": "Tài khoản",
"settings": "Cài đặt",
"inviteMonths": "Tháng",
"inviteDays": "Ngày",
"inviteHours": "Giờ",
"inviteMinutes": "Phút",
"inviteNumberOfUses": "Số lần sử dụng",
"inviteDuration": "Thời hạn hiệu lực",
"warning": "Cảnh báo",
"inviteInfiniteUsesWarning": "các lời mời không giới hạn số lần sử dụng có thể bị lạm dụng",
"inviteSendToEmail": "Gửi tới",
"login": "Đăng nhập",
"logout": "Đăng xuất",
"create": "Tạo mới",
"apply": "Áp dụng",
"delete": "Xóa",
"add": "Thêm",
"select": "Chọn",
"name": "Tên",
"date": "Ngày",
"enabled": "Mở",
"disabled": "Tắt",
"reEnable": "Mở lại",
"setExpiry": "Đặt hết hạn",
"disable": "Tắt",
"admin": "Admin",
"updates": "Cập nhật",
"update": "Cập nhật",
"download": "Tải về",
"search": "Tìm kiếm",
"advancedSettings": "Cài đặt Cấp cao",
"lastActiveTime": "Lần cuối Hoạt động",
"from": "Từ",
"user": "Người dùng",
"expiry": "Hết hạn",
"userExpiry": "Hết hạn Người dùng",
"userExpiryDescription": "Sau một khoảng thời gian nhất định sau khi mỗi đăng ký, jfa-go sẽ xóa/vô hiệu hóa tài khoản. Bạn có thể chỉnh sửa chế độ này trong cài đặt.",
"aboutProgram": "Thông tin",
"version": "Phiên bản",
"commitNoun": "Gửi",
"newUser": "Người dùng mới",
"profile": "Hồ sơ",
"unknown": "Không xác định",
"label": "Nhãn",
"announce": "Thông báo",
"templates": "Mẫu",
"subject": "Chủ đề",
"message": "Tin nhắn",
"variables": "Biến",
"conditionals": "Điều kiện",
"preview": "Xem trước",
"reset": "Đặt lại",
"edit": "Chỉnh sửa",
"donate": "Đóng góp",
"sendPWR": "Gửi Đặt lại Mật khẩu",
"contactThrough": "Liên lạc qua:",
"extendExpiry": "Gia hạn",
"sendPWRManual": "Người dùng {n} không có phương thức liên lạc, nhấn chép để lấy đường link để gửi cho họ.",
"sendPWRSuccess": "Link đặt lại mật khẩu đã được gửi đi.",
"sendPWRSuccessManual": "Nếu người dùng chưa nhận được, nhấn chép để lấy đường link có thể gửi đến họ.",
"sendPWRValidFor": "Link có hiệu lực trong vòng 30 phút.",
"customizeMessages": "Tùy chỉnh Tin nhắn",
"customizeMessagesDescription": "Nếu bạn không muốn sử dụng mẫu tin nhắn của jfa-go, bạn có thể tự tạo mẫu của mình bằng Markdown.",
"markdownSupported": "Có hỗ trợ Markdown.",
"modifySettings": "Chỉnh sửa Cài đặt",
"modifySettingsDescription": "Áp dụng các cài đặt từ một mẫu có sẵn, hoặc lấy trực tiếp từ một người dùng.",
"applyHomescreenLayout": "Áp dụng bố cục trang chủ",
"sendDeleteNotificationEmail": "Gửi tin nhắn thông báo",
"sendDeleteNotifiationExample": "Tài khoản của bạn đã bị xóa.",
"settingsRestart": "Khởi động lại",
"settingsRestarting": "Đang khởi động lại…",
"settingsRestartRequired": "Cần khởi động lại",
"settingsRestartRequiredDescription": "Một số cài đặt bạn đã thay đổi cần phải khởi động lại để có hiệu lực. Khởi động lại ngay hay để sau?",
"settingsApplyRestartLater": "Áp dụng, khởi động lại sau",
"settingsApplyRestartNow": "Áp dụng & khởi động lại",
"settingsApplied": "Cài đặt đã được áp dụng.",
"settingsRefreshPage": "Làm mới trang trong một vài giây nữa.",
"settingsRequiredOrRestartMessage": "Lưu ý: {n} là những cài đặt cần thiết, {n} là những cài đặt cần khởi động lại nếu chúng được thay đổi.",
"settingsSave": "Lưu",
"ombiProfile": "Tài khoản người dùng Ombi",
"ombiUserDefaultsDescription": "Tạo và cài đặt một tài khoản người dùng Ombi, sau đó chọn nó bên dưới. Các cài đặt/quyền sẽ được lưu lại và được áp dụng cho các tài khoản Ombi được tạo bởi jfa-go khi tài khoản mẫy này được chọn.",
"userProfiles": "Thông tin Người dùng",
"userProfilesDescription": "Mẫu tài khoản được áp dụng cho người dùng khi họ tạo tài khoản. Mẫu tài khoản bao gồm các quyền truy cập thư viện và bốc cục trang chủ.",
"userProfilesIsDefault": "Mặc định",
"userProfilesLibraries": "Thư viện",
"addProfile": "Thêm Tài khoản Mẫu",
"addProfileDescription": "Tạo một tài khoản Jellyfin và cấu hình nó, rồi chọn nó bên dưới. Khi tài khoản mẫu này được áp dụng trong lời mời, các người dùng mới sẽ được tạo với các cài đặt tương tự.",
"addProfileNameOf": "Tên Tài khoản mẫu",
"addProfileStoreHomescreenLayout": "Lưu bố cục trang chủ",
"inviteNoUsersCreated": "Chưa có!",
"inviteUsersCreated": "Người dùng đã tạo",
"inviteNoProfile": "Không có Tài khoản mẫu",
"inviteDateCreated": "Tạo",
"inviteRemainingUses": "Số lần sử dụng còn lại",
"inviteNoInvites": "Không có",
"inviteExpiresInTime": "Hết hạn trong {n}",
"notifyEvent": "Thông báo khi:",
"notifyInviteExpiry": "Khi hết hạn",
"notifyUserCreation": "Khi có người dùng được tạo",
"sendPIN": "Yêu cầu người dùng gửi mã PIN bên dưới cho bot.",
"searchDiscordUser": "Gõ tên người dùng Discord để tìm người dùng.",
"findDiscordUser": "Tìm người dùng Discord",
"linkMatrixDescription": "Nhập tên đăng nhập và mật khẩu của người dùng được sử dụng làm bot. Khi hoàn thành, ứng dụng sẽ khởi động lại.",
"matrixHomeServer": "Địa chỉ máy chủ",
"saveAsTemplate": "Lưu thành mẫu",
"deleteTemplate": "Xóa mẫu",
"templateEnterName": "Nhập tên mẫu để lưu mẫu này.",
"logs": "Nhật ký",
"accessJFA": "Truy cập jfa-go"
},
"notifications": {
"changedEmailAddress": "Đã đổi địa chỉ email của {n}.",
"userCreated": "Người dùng {n} đã được tạo.",
"createProfile": "Đã tạo tài khoản mẫu {n}.",
"saveSettings": "Cài đặt đã được lưu",
"saveEmail": "Email đã được lưu.",
"sentAnnouncement": "Thông báo đã được gửi.",
"savedAnnouncement": "Thông báo đã được lưu.",
"setOmbiProfile": "Mẫu tài khoản Ombi đã được lưu trữ.",
"updateApplied": "Cập nhật mới đã được áp dụng, vui lòng khởi động lại.",
"updateAppliedRefresh": "Cập nhật mới đã được áp dụng, vui lòng làm mới lại trang.",
"telegramVerified": "Tài khoản Telegram đã được xác thực.",
"accountConnected": "Tài khoản đã được kết nối.",
"errorConnection": "Không thể kết nối với jfa-go.",
"error401Unauthorized": "Không được phép. Hãy thử làm mới trang.",
"errorSettingsAppliedNoHomescreenLayout": "Cài đặt đã được áp dụng, nhưng việc áp dụng bố cục màn hình chính có thể không thành công.",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorLoginBlank": "",
"errorUnknown": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"errorSaveSettings": "",
"errorLoadSettings": "",
"errorSetOmbiProfile": "",
"errorLoadOmbiUsers": "",
"errorChangedEmailAddress": "",
"errorFailureCheckLogs": "",
"errorPartialFailureCheckLogs": "",
"errorUserCreated": "",
"errorSendWelcomeEmail": "",
"errorApplyUpdate": "",
"errorCheckUpdate": "",
"updateAvailable": "",
"noUpdatesAvailable": ""
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "",
"plural": ""
},
"deleteNUsers": {
"singular": "",
"plural": ""
},
"disableUsers": {
"singular": "",
"plural": ""
},
"reEnableUsers": {
"singular": "",
"plural": ""
},
"addUser": {
"singular": "",
"plural": ""
},
"deleteUser": {
"singular": "",
"plural": ""
},
"deletedUser": {
"singular": "",
"plural": ""
},
"disabledUser": {
"singular": "",
"plural": ""
},
"enabledUser": {
"singular": "",
"plural": ""
},
"announceTo": {
"singular": "",
"plural": ""
},
"appliedSettings": {
"singular": "",
"plural": ""
},
"extendExpiry": {
"singular": "",
"plural": ""
},
"extendedExpiry": {
"singular": "",
"plural": ""
}
}
}

217
lang/admin/zh-Hant.json Normal file
View File

@@ -0,0 +1,217 @@
{
"meta": {
"name": "繁體中文 (TW)"
},
"strings": {
"invites": "邀請",
"accounts": "帳戶",
"settings": "設置",
"inviteMonths": "月",
"inviteDays": "日",
"inviteHours": "小時",
"inviteMinutes": "分鐘",
"inviteNumberOfUses": "使用次數",
"inviteDuration": "邀請時長",
"warning": "警告",
"inviteInfiniteUsesWarning": "無限使用次數的邀請碼可能被濫用",
"inviteSendToEmail": "發送到",
"login": "登錄",
"logout": "登出",
"create": "創建",
"apply": "應用",
"delete": "刪除",
"add": "添加",
"select": "選擇",
"name": "帳戶名稱",
"date": "日期",
"enabled": "已啟用",
"disabled": "已禁用",
"reEnable": "重新啟用",
"setExpiry": "設置到期時間",
"disable": "禁用",
"admin": "管理員",
"updates": "更新",
"update": "更新",
"download": "下載",
"search": "搜尋",
"advancedSettings": "高級設置",
"lastActiveTime": "上次啟用時間",
"from": "從",
"user": "帳戶",
"expiry": "到期",
"userExpiry": "帳戶到期",
"userExpiryDescription": "每次註冊后指定的時間jfa-go 將刪除/禁用該帳戶。您可以在設定中更改此行為。",
"aboutProgram": "關於",
"version": "版本",
"commitNoun": "提交",
"newUser": "新帳戶",
"profile": "帳戶資料",
"unknown": "未知",
"label": "標籤",
"logs": "日誌",
"announce": "公告",
"templates": "範本",
"subject": "主題",
"message": "訊息",
"variables": "變數",
"conditionals": "條件",
"preview": "預覽",
"reset": "重設",
"edit": "編輯",
"donate": "捐贈",
"sendPWR": "發送密碼重置",
"contactThrough": "聯繫方式:",
"extendExpiry": "延長到期時間",
"sendPWRManual": "使用者 {n} 沒有聯繫方式,請按 “複製” 以獲取連結並手動發送給使用者。",
"sendPWRSuccess": "已發送密碼重置連結。",
"sendPWRSuccessManual": "如果使用者沒收到,請按 “複製” 以獲取連結並手動發送給使用者。",
"sendPWRValidFor": "該連結的有效期為 30 分鐘。",
"customizeMessages": "自定義訊息",
"customizeMessagesDescription": "如果您不想使用 jfa-go 的訊息範本,可以使用 Markdown 創建自己的訊息範本。",
"markdownSupported": "支持 Markdown。",
"modifySettings": "修改設置",
"modifySettingsDescription": "應用現有配置文件中的設置,或直接從帳戶處獲取設置。",
"applyHomescreenLayout": "應用主螢幕佈局",
"sendDeleteNotificationEmail": "發送通知訊息",
"sendDeleteNotifiationExample": "您的帳戶已被刪除。",
"settingsRestart": "重新啟動",
"settingsRestarting": "正在重新啟動…",
"settingsRestartRequired": "需要重新啟動",
"settingsRestartRequiredDescription": "需要重新啟動才能應用您更改的某些設定。 現在重新啟動還是稍後重新啟動?",
"settingsApplyRestartLater": "應用,稍後重新啟動",
"settingsApplyRestartNow": "應用並重新啟動",
"settingsApplied": "已應用設置。",
"settingsRefreshPage": "幾秒鐘後刷新頁面。",
"settingsRequiredOrRestartMessage": "注意: {n} 表示必填欄位, {n} 表示更改需要重新啟動。",
"settingsSave": "儲存",
"ombiProfile": "Ombi 帳戶資料",
"ombiUserDefaultsDescription": "創建一個 Ombi 帳戶並對其進行配置,然後在下面選擇它。選擇此設定時,它的設置/權限將被存儲並應用於由 jfa-go 創建的新 Ombi 帳戶。",
"userProfiles": "帳戶資料",
"userProfilesDescription": "配置文件在使用者創建帳戶時應用於使用者。配置文件包括庫訪問權限和主螢幕佈局。",
"userProfilesIsDefault": "預設",
"userProfilesLibraries": "庫",
"addProfile": "添加配置文件",
"addProfileDescription": "創建一個 Jellyfin 帳戶並對其進行配置,然後在下面選擇它。將此設定文件應用於邀請時,將使用這些設置創建新帳戶。",
"addProfileNameOf": "配置文件名稱",
"addProfileStoreHomescreenLayout": "保存主螢幕佈局",
"inviteNoUsersCreated": "暫無!",
"inviteUsersCreated": "創建的帳戶",
"inviteNoProfile": "無資料",
"inviteDateCreated": "已創建",
"inviteRemainingUses": "剩餘使用次數",
"inviteNoInvites": "無",
"inviteExpiresInTime": "在 {n} 到期",
"notifyEvent": "通知:",
"notifyInviteExpiry": "在到期時",
"notifyUserCreation": "在創建用戶時",
"sendPIN": "要求使用者將下面的 PIN 發送給機器人。",
"searchDiscordUser": "開始鍵入 Discord 帳戶名稱以查找帳戶。",
"findDiscordUser": "查尋 Discord 帳戶",
"linkMatrixDescription": "輸入要用作機器人的帳戶的帳戶名稱和密碼。提交后,應用程序將重新啟動。",
"matrixHomeServer": "主伺服器位址",
"saveAsTemplate": "儲存為範本",
"deleteTemplate": "刪除範本",
"templateEnterName": "輸入名稱以儲存此範本。",
"accessJFA": "訪問 jfa-go",
"accessJFASettings": "無法更改,因為已在「一般設置」中設置了「僅限管理員帳戶」或「允許全部帳戶」。"
},
"notifications": {
"changedEmailAddress": "更改了 {n} 的電子郵件地址。",
"userCreated": "帳戶 {n} 已創建。",
"createProfile": "創建了配置文件 {n}。",
"saveSettings": "設置已儲存",
"saveEmail": "電子郵件已儲存。",
"sentAnnouncement": "已發送公告。",
"savedAnnouncement": "公告已儲存。",
"setOmbiProfile": "ombi 設定已存儲。",
"updateApplied": "已應用更新,請重新啟動。",
"updateAppliedRefresh": "更新已應用,請重新整理。",
"telegramVerified": "Telegram 帳戶已驗證。",
"accountConnected": "帳戶已連接。",
"errorConnection": "無法連接到 jfa-go。",
"error401Unauthorized": "未經授權。嘗試重新整理頁面。",
"errorSettingsAppliedNoHomescreenLayout": "已應用設置,但應用主螢幕佈局可能失敗。",
"errorHomescreenAppliedNoSettings": "已應用主螢幕佈局,但應用設置可能失敗。",
"errorSettingsFailed": "應用失敗。",
"errorLoginBlank": "帳戶名稱和/或密碼留空。",
"errorUnknown": "未知的錯誤。",
"errorSaveEmail": "無法儲存電子郵件。",
"errorBlankFields": "欄位留空",
"errorDeleteProfile": "無法刪除設置文件 {n}",
"errorLoadProfiles": "無法讀取設置文件。",
"errorCreateProfile": "無法創建設置文件 {n}",
"errorSetDefaultProfile": "無法設置預設設置文件。",
"errorLoadUsers": "無法讀取帳戶。",
"errorSaveSettings": "無法儲存設置。",
"errorLoadSettings": "無法讀取設置。",
"errorSetOmbiProfile": "無法儲存 ombi 設置文件。",
"errorLoadOmbiUsers": "無法讀取 ombi 帳戶。",
"errorChangedEmailAddress": "無法更改 {n} 的電子郵件地址。",
"errorFailureCheckLogs": "失敗(請檢查主控台/紀錄)",
"errorPartialFailureCheckLogs": "部分故障(請檢查主控台/日誌)",
"errorUserCreated": "無法創建帳戶 {n}。",
"errorSendWelcomeEmail": "無法送出歡迎訊息(請檢查主控台/紀錄)",
"errorApplyUpdate": "無法應用更新,請手動嘗試。",
"errorCheckUpdate": "無法檢查更新。",
"updateAvailable": "有新的更新可用,請檢查設置。",
"noUpdatesAvailable": "沒有新的更新可用。"
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "修改 {n} 個帳戶的設置",
"plural": "修改 {n} 個帳戶的設置"
},
"deleteNUsers": {
"singular": "刪除 {n} 個帳戶",
"plural": "刪除 {n} 個帳戶"
},
"disableUsers": {
"singular": "禁用 {n} 個帳戶",
"plural": "禁用 {n} 個帳戶"
},
"reEnableUsers": {
"singular": "重新啟用 {n} 個帳戶",
"plural": "重新啟用 {n} 個帳戶"
},
"addUser": {
"singular": "添加帳戶",
"plural": "添加帳戶"
},
"deleteUser": {
"singular": "刪除帳戶",
"plural": "刪除帳戶"
},
"deletedUser": {
"singular": "刪除 {n} 個帳戶。",
"plural": "刪除 {n} 個帳戶。"
},
"disabledUser": {
"singular": "禁用 {n} 個帳戶。",
"plural": "禁用 {n} 個帳戶。"
},
"enabledUser": {
"singular": "已啟用 {n} 個帳戶。",
"plural": "已啟用 {n} 個帳戶。"
},
"announceTo": {
"singular": "公告給 {n} 個帳戶",
"plural": "公告給 {n} 個帳戶"
},
"appliedSettings": {
"singular": "將設置應用到 {n} 個帳戶。",
"plural": "將設置應用到 {n} 個帳戶。"
},
"extendExpiry": {
"singular": "延長 {n} 個帳戶的到期時間",
"plural": "延長 {n} 個帳戶的到期時間"
},
"setExpiry": {
"singular": "設置 {n} 個帳戶的到期時間",
"plural": "設置 {n} 個帳戶的到期時間"
},
"extendedExpiry": {
"singular": "已延長 {n} 個帳戶的到期時間。",
"plural": "已延長 {n} 個帳戶的到期時間。"
}
}
}

View File

@@ -75,130 +75,146 @@
"settingsApplyRestartNow": "应用并重启",
"settingsApplied": "已应用设置。",
"settingsRefreshPage": "几秒钟后刷新页面。",
"settingsRequiredOrRestartMessage": "",
"settingsSave": "",
"ombiUserDefaults": "",
"ombiUserDefaultsDescription": "",
"userProfiles": "",
"userProfilesDescription": "",
"userProfilesIsDefault": "",
"userProfilesLibraries": "",
"addProfile": "",
"addProfileDescription": "",
"addProfileNameOf": "",
"addProfileStoreHomescreenLayout": "",
"inviteNoUsersCreated": "",
"inviteUsersCreated": "",
"inviteNoProfile": "",
"inviteDateCreated": "",
"inviteRemainingUses": "",
"inviteNoInvites": "",
"inviteExpiresInTime": "",
"notifyEvent": "",
"notifyInviteExpiry": "",
"notifyUserCreation": "",
"sendPIN": "",
"searchDiscordUser": "",
"findDiscordUser": "",
"linkMatrixDescription": "",
"matrixHomeServer": "",
"saveAsTemplate": "",
"deleteTemplate": "",
"templateEnterName": ""
"settingsRequiredOrRestartMessage": "注意:{n} 表示必填字段,{n} 表示更改需要重新启动。",
"settingsSave": "保存",
"ombiUserDefaults": "Ombi 用户默认值",
"ombiUserDefaultsDescription": "创建并配置 Ombi 用户,然后在下面选择它。它的设置/权限将被存储并应用于由 jfa-go 创建的新 Ombi 用户。",
"userProfiles": "用户档案",
"userProfilesDescription": "配置文件在用户创建帐户时应用于用户。配置文件包括库访问权限和主屏幕布局。",
"userProfilesIsDefault": "默认",
"userProfilesLibraries": "",
"addProfile": "添加档案",
"addProfileDescription": "创建一个 Jellyfin 用户并配置它,然后在下面选择它。将此配置文件应用于邀请时,将使用这些设置创建新用户。",
"addProfileNameOf": "配置文件名称",
"addProfileStoreHomescreenLayout": "保存主屏幕布局",
"inviteNoUsersCreated": "暂时不!",
"inviteUsersCreated": "已创建的用户",
"inviteNoProfile": "没有个人资料",
"inviteDateCreated": "已创建",
"inviteRemainingUses": "剩余使用次数",
"inviteNoInvites": "",
"inviteExpiresInTime": "在 {n} 到期",
"notifyEvent": "通知:",
"notifyInviteExpiry": "在到期时",
"notifyUserCreation": "在创建用户时",
"sendPIN": "需要用户将下面的 PIN 发送给机器人。",
"searchDiscordUser": "开始输入 Discord 用户名以查找用户。",
"findDiscordUser": "查找 Discord 用户",
"linkMatrixDescription": "输入要用作机器人的用户的用户名和密码。一旦提交,应用程序将重新启动。",
"matrixHomeServer": "主服务器地址",
"saveAsTemplate": "保存为模板",
"deleteTemplate": "删除模板",
"templateEnterName": "输入名称以保存此模板。",
"sendPWRManual": "用户 {n} 没有联系方式,请按下钮复制能发给用户的链接。",
"sendPWRSuccess": "密码重置链接已发了。",
"sendPWR": "发送密码重置",
"sendPWRSuccessManual": "如果用户没收到,请按下钮复制链接,手动发给用户。",
"setExpiry": "设置到期",
"logs": "记录",
"sendPWRValidFor": "此链接有效30分钟。",
"ombiProfile": "Ombi 用户配置文件",
"accessJFASettings": "无法更改,因为“仅限管理员”或“允许所有”已在“设置”>“常规”中设置。",
"accessJFA": "访问jfa-go"
},
"notifications": {
"changedEmailAddress": "",
"userCreated": "",
"createProfile": "",
"saveSettings": "",
"saveEmail": "",
"sentAnnouncement": "",
"savedAnnouncement": "",
"setOmbiDefaults": "",
"updateApplied": "",
"updateAppliedRefresh": "",
"telegramVerified": "",
"accountConnected": "",
"errorConnection": "",
"error401Unauthorized": "",
"errorSettingsAppliedNoHomescreenLayout": "",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorLoginBlank": "",
"errorUnknown": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"errorSaveSettings": "",
"errorLoadSettings": "",
"errorSetOmbiDefaults": "",
"errorLoadOmbiUsers": "",
"errorChangedEmailAddress": "",
"errorFailureCheckLogs": "",
"errorPartialFailureCheckLogs": "",
"errorUserCreated": "",
"errorSendWelcomeEmail": "",
"errorApplyUpdate": "",
"errorCheckUpdate": "",
"updateAvailable": "",
"noUpdatesAvailable": ""
"changedEmailAddress": "更改了 {n} 的电子邮件地址。",
"userCreated": "用户 {n} 已创建。",
"createProfile": "创建了配置文件{n}。",
"saveSettings": "设置已保存",
"saveEmail": "电子邮件已保存。",
"sentAnnouncement": "公告已发出。",
"savedAnnouncement": "公告已保存。",
"setOmbiDefaults": "存储的ombi默认值。",
"updateApplied": "已应用更新,请重新启动。",
"updateAppliedRefresh": "已应用更新,请刷新。",
"telegramVerified": "Telegram账户已验证。",
"accountConnected": "帐户已连接。",
"errorConnection": "无法连接到 jfa-go。",
"error401Unauthorized": "无授权。尝试刷新页面。",
"errorSettingsAppliedNoHomescreenLayout": "已应用设置,但应用主屏幕布局可能失败。",
"errorHomescreenAppliedNoSettings": "已应用主屏幕布局,但应用设置可能失败。",
"errorSettingsFailed": "应用失败。",
"errorLoginBlank": "用户名/密码留空。",
"errorUnknown": "未知错误。",
"errorSaveEmail": "电子邮箱保存失败。",
"errorBlankFields": "字段留空",
"errorDeleteProfile": "删除配置文件{n}失败",
"errorLoadProfiles": "加载配置文件失败。",
"errorCreateProfile": "创建配置文件{n}失败",
"errorSetDefaultProfile": "设置默认配置文件失败。",
"errorLoadUsers": "加载用户列表失败。",
"errorSaveSettings": "无法保存设置。",
"errorLoadSettings": "加载配置列表失败。",
"errorSetOmbiDefaults": "存储Ombi默认值失败。",
"errorLoadOmbiUsers": "加载ombi用户列表失败。",
"errorChangedEmailAddress": "无法更改 {n} 的电子邮件地址。",
"errorFailureCheckLogs": "失败(检查控制台/日志)",
"errorPartialFailureCheckLogs": "部分失败(检查控制台/日志)",
"errorUserCreated": "创建用户{n}失败。",
"errorSendWelcomeEmail": "发送欢迎消息失败(检查控制台/日志)",
"errorApplyUpdate": "无法应用更新,请手动尝试。",
"errorCheckUpdate": "检查更新失败。",
"updateAvailable": "有新更新可用,请检查设置。",
"noUpdatesAvailable": "没有可用的更新。",
"setOmbiProfile": "保存ombi配置文件。",
"errorSetOmbiProfile": "无法保存ombi配置文件。"
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "",
"plural": ""
"singular": "修改{n}用户的设置",
"plural": "修改{n}用户列表的设置"
},
"deleteNUsers": {
"singular": "",
"plural": ""
"singular": "删除 {n} 个用户",
"plural": "删除 {n} 个用户"
},
"disableUsers": {
"singular": "",
"plural": ""
"singular": "禁用 {n} 个用户",
"plural": "禁用 {n} 个用户"
},
"reEnableUsers": {
"singular": "",
"plural": ""
"singular": "重新启用 {n} 个用户",
"plural": "重新启用 {n} 个用户"
},
"addUser": {
"singular": "",
"plural": ""
"singular": "添加用户",
"plural": "添加用户"
},
"deleteUser": {
"singular": "",
"plural": ""
"singular": "删除用户",
"plural": "删除用户"
},
"deletedUser": {
"singular": "",
"plural": ""
"singular": "删除了 {n} 个用户。",
"plural": "删除了 {n} 个用户。"
},
"disabledUser": {
"singular": "",
"plural": ""
"singular": "禁用 了{n} 个用户。",
"plural": "禁用 了{n} 个用户。"
},
"enabledUser": {
"singular": "",
"plural": ""
"singular": "启用了{n} 个用户。",
"plural": "启用了{n} 个用户。"
},
"announceTo": {
"singular": "",
"plural": ""
"singular": "通知 {n} 位用户",
"plural": "通知 {n} 位用户"
},
"appliedSettings": {
"singular": "",
"plural": ""
"singular": "将设置应用到 {n} 个用户。",
"plural": "将设置应用到 {n} 个用户。"
},
"extendExpiry": {
"singular": "",
"plural": ""
"singular": "延长 {n} 个用户的有效期",
"plural": "延长 {n} 个用户的有效期"
},
"extendedExpiry": {
"singular": "",
"plural": ""
"singular": "延长了 {n} 个用户的有效期。",
"plural": "延长了 {n} 个用户的有效期。"
},
"setExpiry": {
"plural": "为{n}用户设置到期时间",
"singular": "为{n}用户设置到期时间"
}
}
}

View File

@@ -1,6 +1,6 @@
{
"meta": {
"name": "Dansk"
"name": "Dansk (DK)"
},
"strings": {
"username": "Brugernavn",
@@ -10,6 +10,7 @@
"submit": "Indsend",
"send": "Send",
"success": "Succes",
"continue": "Fortsæt",
"error": "Fejl",
"copy": "Kopiér",
"copied": "Kopiret",
@@ -21,6 +22,8 @@
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Kontakt gennem Discord",
"theme": "Tema"
"theme": "Tema",
"refresh": "Opdater",
"required": "Påkrævet"
}
}

View File

@@ -6,9 +6,10 @@
"username": "Benutzername",
"name": "Name",
"password": "Passwort",
"emailAddress": "E-Mail-Adresse",
"emailAddress": "E-Mail Adresse",
"submit": "Absenden",
"success": "Erfolgreich",
"continue": "Weiter",
"error": "Fehler",
"copy": "Kopieren",
"theme": "Thema",
@@ -21,6 +22,8 @@
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"send": "Senden",
"contactDiscord": "Kontakt über Discord"
"contactDiscord": "Kontakt über Discord",
"refresh": "Aktualisieren",
"required": "Erforderlich"
}
}

View File

@@ -9,6 +9,7 @@
"name": "Όνομα",
"submit": "Καταχώρηση",
"success": "Επιτυχία",
"continue": "Συνέχεια",
"error": "Σφάλμα",
"copy": "Αντιγραφή",
"theme": "Θέμα",

View File

@@ -1,5 +1,29 @@
{
"meta": {
"name": "English (GB)"
},
"strings": {
"continue": "Continue",
"time24h": "24h Time",
"linkTelegram": "Link Telegram",
"send": "Send",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord",
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"copy": "Copy",
"copied": "Copied",
"submit": "Submit",
"success": "Success",
"error": "Error",
"time12h": "12h Time",
"theme": "Theme",
"contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram",
"name": "Name",
"refresh": "Refresh",
"required": "Required"
}
}

View File

@@ -10,6 +10,7 @@
"submit": "Submit",
"send": "Send",
"success": "Success",
"continue": "Continue",
"error": "Error",
"copy": "Copy",
"copied": "Copied",
@@ -21,6 +22,8 @@
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord",
"theme": "Theme"
"theme": "Theme",
"refresh": "Refresh",
"required": "Required"
}
}

View File

@@ -1,19 +1,29 @@
{
"meta": {
"name": "Español(ES)"
"name": "Español (ES)"
},
"strings": {
"username": "Nombre de usuario",
"password": "Contraseña",
"emailAddress": "Dirección de correo electrónico",
"emailAddress": "Correo electrónico",
"name": "Nombre",
"submit": "Enviar",
"success": "Éxito",
"continue": "Continuar",
"error": "Error",
"copy": "Copiar",
"copied": "Copiado",
"time24h": "24 horas",
"time12h": "24 horas",
"theme": "Tema"
"time24h": "Formato de 24 horas",
"time12h": "Formato de 12 horas",
"theme": "Tema",
"send": "Enviar",
"contactDiscord": "Contactar por Discord",
"contactEmail": "Contactar por correo electrónico",
"contactTelegram": "Contactar por Telegram",
"linkMatrix": "Enlace Matrix",
"linkDiscord": "Enlace Discord",
"linkTelegram": "Enlace Telegram",
"refresh": "Refrescar",
"required": "Requerido"
}
}

View File

@@ -10,6 +10,7 @@
"submit": "تایید",
"send": "ارسال",
"success": "موفقیت",
"continue": "ادامه دادن",
"error": "خطا",
"copy": "کپی",
"copied": "کپی شد",

View File

@@ -10,6 +10,7 @@
"emailAddress": "Addresse Email",
"submit": "Soumettre",
"success": "Succès",
"continue": "Continuer",
"error": "Erreur",
"copy": "Copier",
"time24h": "Temps 24h",

View File

@@ -9,10 +9,12 @@
"name": "Nama",
"submit": "Submit",
"success": "Sukses",
"continue": "Lanjut",
"error": "Error",
"copy": "Salin",
"time24h": "Waktu 24 jam",
"time12h": "Waktu 12 jam",
"theme": "Tema"
"theme": "Tema",
"send": "Kirim"
}
}

29
lang/common/it-IT.json Normal file
View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "Inglese (US)"
},
"strings": {
"username": "Username",
"password": "Password",
"emailAddress": "Indirizzo Email",
"name": "Nome",
"submit": "Invia",
"send": "Invia",
"success": "Successo",
"continue": "Continua",
"error": "Errore",
"copy": "Copia",
"copied": "Copiato",
"time24h": "Formato 24h",
"time12h": "Formato 12h",
"linkTelegram": "Link Telegram",
"contactEmail": "Contatta tramite Email",
"contactTelegram": "Contatta tramite Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contatta tramite Discord",
"theme": "Tema",
"refresh": "Aggiorna",
"required": "Richiesto"
}
}

View File

@@ -9,6 +9,7 @@
"emailAddress": "E-mailadres",
"submit": "Verstuur",
"success": "Succes",
"continue": "Doorgaan",
"error": "Fout",
"copy": "Kopiëer",
"theme": "Thema",
@@ -21,6 +22,8 @@
"send": "Verstuur",
"linkDiscord": "Koppel Discord",
"linkMatrix": "Koppel Matrix",
"contactDiscord": "Stuur Discord bericht"
"contactDiscord": "Stuur Discord bericht",
"refresh": "Ververs",
"required": "Verplicht"
}
}

29
lang/common/pl-PL.json Normal file
View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "Polski (PL)"
},
"strings": {
"username": "Nazwa Użytkownika",
"password": "Hasło",
"emailAddress": "E-mail",
"name": "Imię",
"submit": "Akceptuj",
"send": "Wyślij",
"success": "Sukces",
"continue": "Kontynuuj",
"error": "Błąd",
"copy": "Kopiuj",
"copied": "Skopiowano",
"time24h": "24 godziny",
"time12h": "12 godzin",
"linkTelegram": "Link Telegram",
"contactEmail": "Kontakt drogą mailową",
"contactTelegram": "Kontakt przez Telegram",
"linkDiscord": "Link do Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Kontakt przez Discord",
"theme": "Motyw",
"refresh": "Odśwież",
"required": "Wymagane"
}
}

View File

@@ -6,9 +6,10 @@
"username": "Nome do Usuário",
"name": "Nome",
"password": "Senha",
"emailAddress": "Endereço de Email",
"emailAddress": "Endereço de e-mail",
"submit": "Enviar",
"success": "Sucesso",
"continue": "Continuar",
"error": "Erro",
"copy": "Copiar",
"theme": "Tema",
@@ -21,6 +22,8 @@
"send": "Enviar",
"linkDiscord": "Link do Discord",
"linkMatrix": "Link do Matrix",
"contactDiscord": "Contato através do Discord"
"contactDiscord": "Contato através do Discord",
"refresh": "Atualizar",
"required": "Requeridos"
}
}

29
lang/common/sl-si.json Normal file
View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "Angleščina (ZDA)"
},
"strings": {
"username": "Uporabniško ime",
"password": "Geslo",
"emailAddress": "E-poštni naslov",
"name": "Ime",
"submit": "Potrdi",
"send": "Pošlji",
"success": "Uspeh",
"continue": "Nadaljuj",
"error": "Napaka",
"copy": "Kopiraj",
"copied": "Kopirano",
"time24h": "24ur format čas",
"time12h": "12ur format časa",
"linkTelegram": "Poveži Telegram",
"contactEmail": "Kontakt preko e-pošte",
"contactTelegram": "Kontakt preko Telegrama",
"linkDiscord": "Poveži Discord",
"linkMatrix": "Poveži Matrix",
"contactDiscord": "Kontakt preko Discorda",
"theme": "Tema",
"refresh": "Osveži",
"required": "Obvezno"
}
}

View File

@@ -9,6 +9,7 @@
"name": "Namn",
"submit": "Skicka",
"success": "Lyckades",
"continue": "Fortsätt",
"error": "Fel",
"copy": "Kopiera",
"time24h": "24 timmarsklocka",

29
lang/common/zh-Hant.json Normal file
View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "繁體中文 (TW)"
},
"strings": {
"username": "帳戶名稱",
"password": "密碼",
"emailAddress": "電子郵件",
"name": "帳戶名稱",
"submit": "提交",
"send": "發送",
"success": "成功",
"continue": "繼續",
"error": "錯誤",
"copy": "複製",
"copied": "已複製",
"time24h": "24小時制",
"time12h": "12小時制",
"linkTelegram": "連結 Telegram",
"contactEmail": "通過電子郵件聯繫",
"contactTelegram": "通過 Telegram 聯繫",
"linkDiscord": "連結 Discord",
"linkMatrix": "連結 Matrix",
"contactDiscord": "通過 Discord 聯繫",
"theme": "主題",
"refresh": "重新整理",
"required": "必填"
}
}

View File

@@ -10,6 +10,7 @@
"submit": "提交",
"send": "发送",
"success": "成功",
"continue": "继续",
"error": "错误",
"copy": "复制",
"copied": "已复制",
@@ -21,6 +22,8 @@
"linkDiscord": "关联Discord",
"linkMatrix": "关联Matrix",
"contactDiscord": "通过Discord联系",
"theme": "主题"
"theme": "主题",
"refresh": "刷新",
"required": "必需的"
}
}

View File

@@ -1,6 +1,6 @@
{
"meta": {
"name": "Dansk"
"name": "Dansk (DK)"
},
"strings": {
"ifItWasNotYou": "Ignorer venligst hvis dette ikke var dig.",
@@ -12,14 +12,14 @@
"title": "Meddelelse: Bruger oprettet",
"aUserWasCreated": "En bruger blev oprettet med koden {code}.",
"time": "Tid",
"notificationNotice": "Meddelelse: Notifikations e-mails kan blive ændret på admin-siden."
"notificationNotice": "Bemærk: Notifikations beskeder kan blive ændret på admin-siden."
},
"inviteExpiry": {
"name": "Invitationens udløb",
"title": "Meddelelse: Invitation udløbet",
"inviteExpired": "Invitation udløbet.",
"expiredAt": "Koden {code} udløber om {time}.",
"notificationNotice": "Meddelelse: Notifikations e-mails kan blive ændret på admin-siden."
"notificationNotice": "Bemærk: Notifikations beskeder kan blive ændret på admin-siden."
},
"passwordReset": {
"name": "Nulstil Adgangskode",

View File

@@ -11,14 +11,14 @@
"title": "Mitteilung: Benutzer erstellt",
"aUserWasCreated": "Ein Benutzer wurde unter Verwendung des Codes {code} erstellt.",
"time": "Zeit",
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden.",
"notificationNotice": "Hinweis: Benachrichtigungen können auf dem Administrator-Dashboard umgeschalten werden.",
"name": "Benutzererstellung"
},
"inviteExpiry": {
"title": "Mitteilung: Invite abgelaufen",
"inviteExpired": "Invite abgelaufen.",
"expiredAt": "Code {code} lief um {time} ab.",
"notificationNotice": "Hinweis: Benachrichtigungs-E-Mails können auf dem Administrator-Dashboard umgeschalten werden.",
"notificationNotice": "Hinweis: Benachrichtigungen können auf dem Administrator-Dashboard umgeschalten werden.",
"name": "Invite Ablaufdatum"
},
"passwordReset": {

View File

@@ -1,5 +1,77 @@
{
"meta": {
"name": "English (GB)"
},
"emailConfirmation": {
"title": "Confirm your email - Jellyfin",
"confirmEmail": "Confirm Email",
"name": "Confirmation email",
"clickBelow": "Click the link below to confirm your email address and start using Jellyfin."
},
"welcomeEmail": {
"name": "Welcome",
"title": "Welcome to Jellyfin",
"welcome": "Welcome to Jellyfin!",
"yourAccountWillExpire": "Your account will expire on {date}.",
"jellyfinURL": "URL",
"youCanLoginWith": "You can login with the details below"
},
"strings": {
"helloUser": "Hi {username},",
"ifItWasNotYou": "If this wasn't you, please ignore this.",
"reason": "Reason"
},
"userCreated": {
"name": "User creation",
"title": "Notice: User created",
"time": "Time",
"aUserWasCreated": "A user was created using code {code}.",
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard."
},
"inviteExpiry": {
"expiredAt": "Code {code} expired at {time}.",
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard.",
"name": "Invite expiry",
"title": "Notice: Invite expired",
"inviteExpired": "Invite expired."
},
"passwordReset": {
"name": "Password reset",
"title": "Password reset requested - Jellyfin",
"ifItWasYou": "If this was you, enter the pin below into the prompt.",
"ifItWasYouLink": "If this was you, click the link below.",
"pin": "PIN",
"someoneHasRequestedReset": "Someone has recently requested a password reset on Jellyfin.",
"codeExpiry": "The code will expire on {date}, at {time} UTC, which is in {expiresInMinutes}."
},
"userDeleted": {
"title": "Your account was deleted - Jellyfin",
"yourAccountWasDeleted": "Your Jellyfin account was deleted.",
"name": "User deletion"
},
"inviteEmail": {
"name": "Invite email",
"hello": "Hi",
"toJoin": "To join, follow the below link.",
"inviteExpiry": "This invite will expire on {date} at {time}, which is in {expiresInMinutes}, so act quick.",
"linkButton": "Setup your account",
"title": "Invite - Jellyfin",
"youHaveBeenInvited": "You've been invited to Jellyfin."
},
"userExpired": {
"name": "User expiry",
"title": "Your account has expired - Jellyfin",
"yourAccountHasExpired": "Your account has expired.",
"contactTheAdmin": "Contact the administrator for more info."
},
"userDisabled": {
"name": "User disabled",
"title": "Your account has been disabled - Jellyfin",
"yourAccountWasDisabled": "Your account was disabled."
},
"userEnabled": {
"name": "User enabled",
"title": "Your account has been re-enabled - Jellyfin",
"yourAccountWasEnabled": "Your account was re-enabled."
}
}

View File

@@ -12,14 +12,14 @@
"title": "Notice: User created",
"aUserWasCreated": "A user was created using code {code}.",
"time": "Time",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard."
},
"inviteExpiry": {
"name": "Invite expiry",
"title": "Notice: Invite expired",
"inviteExpired": "Invite expired.",
"expiredAt": "Code {code} expired at {time}.",
"notificationNotice": "Note: Notification emails can be toggled on the admin dashboard."
"notificationNotice": "Note: Notification messages can be toggled on the admin dashboard."
},
"passwordReset": {
"name": "Password reset",

View File

@@ -1,33 +1,33 @@
{
"meta": {
"name": "Español(ES)"
"name": "Español (ES)"
},
"strings": {
"ifItWasNotYou": "Si no fue usted, ignore este correo electrónico.",
"ifItWasNotYou": "Si no fue usted, ignórelo.",
"helloUser": "Hola {username},",
"reason": "Razón"
},
"userCreated": {
"name": "Creación de usuarios",
"title": "Noticia: Usuario creado",
"title": "Aviso: Usuario creado",
"aUserWasCreated": "Se creó un usuario con el código {code}.",
"time": "Hora",
"notificationNotice": "Nota: los correos electrónicos de notificación se pueden alternar en el panel de administración."
"notificationNotice": "Nota: Los mensajes de notificación se pueden alternar en el panel de administración."
},
"inviteExpiry": {
"name": "Vencimiento de la invitación",
"title": "Aviso: Invitación caducada",
"inviteExpired": "Invitación caducada.",
"expiredAt": "El código {code} venció a las {time}.",
"notificationNotice": "Nota: Los correos electrónicos de notificación se pueden alternar en el panel de administración."
"notificationNotice": "Nota: Los mensajes de notificación se pueden alternar en el panel de administración."
},
"passwordReset": {
"name": "Restablecimiento de contraseña",
"title": "Solicitud de restablecimiento de contraseña - Jellyfin",
"someoneHasRequestedReset": "Alguien ha solicitado recientemente un restablecimiento de contraseña en Jellyfin.",
"ifItWasYou": "Si era usted, ingrese el pin a continuación en el mensaje.",
"ifItWasYou": "Si era usted, ingrese el PIN a continuación en el mensaje.",
"ifItWasYouLink": "Si fue usted, haga clic en el enlace de abajo.",
"codeExpiry": "El código vencerá el {date}, a las {time} UTC, que es en {expiresInMinutes}.",
"codeExpiry": "El código vencerá el {date}, a las {time} UTC, que es en {expiresInMinutes}.",
"pin": "PIN"
},
"userDeleted": {
@@ -36,26 +36,26 @@
"yourAccountWasDeleted": "Su cuenta de Jellyfin fue eliminada."
},
"userDisabled": {
"name": "Usuario deshabilitado",
"title": "Su cuenta ha sido deshabilitada - Jellyfin",
"yourAccountWasDisabled": "Su cuenta fue inhabilitada."
"name": "Usuario desactivado",
"title": "Su cuenta ha sido desactivada - Jellyfin",
"yourAccountWasDisabled": "Su cuenta fue desactivada."
},
"userEnabled": {
"name": "Usuario habilitado",
"name": "Usuario activado",
"title": "Su cuenta ha sido reactivada - Jellyfin",
"yourAccountWasEnabled": "Su cuenta se volvió a habilitar."
"yourAccountWasEnabled": "Su cuenta fue reactivada."
},
"inviteEmail": {
"name": "Correo electrónico",
"title": "Invitar - Jellyfin",
"name": "Invitar por correo electrónico",
"title": "Invitación - Jellyfin",
"hello": "Hola",
"youHaveBeenInvited": "Has sido invitado a Jellyfin.",
"toJoin": "Para unirse, siga el enlace a continuación.",
"inviteExpiry": "Esta invitación vencerá el {date} a las {time}, que es en {expiresInMinutes}, así que regístrese cuanto antes.",
"linkButton": "Configurar tu cuenta"
"inviteExpiry": "Esta invitación vencerá el {date} a las {time}, que es en {expiresInMinutes}, así que regístrese cuanto antes.",
"linkButton": "Configura tu cuenta"
},
"welcomeEmail": {
"name": "Correo de bienvenida",
"name": "Bienvenido",
"title": "Bienvenido a Jellyfin",
"welcome": "¡Bienvenido a Jellyfin!",
"youCanLoginWith": "Puede iniciar sesión con los detalles a continuación",
@@ -66,12 +66,12 @@
"name": "Email de confirmación",
"title": "Confirma tu correo electrónico - Jellyfin",
"clickBelow": "Haga clic en el enlace de abajo para confirmar su dirección de correo electrónico y comenzar a usar Jellyfin.",
"confirmEmail": "Confirmar correo electrónico"
"confirmEmail": "Confirma el correo electrónico"
},
"userExpired": {
"name": "Caducidad del usuario",
"title": "Tu cuenta ha caducado - Jellyfin",
"yourAccountHasExpired": "Tu cuenta ha expirado.",
"contactTheAdmin": "Comuníquese con el administrador para obtener más información."
"contactTheAdmin": "Póngase en contacto con el administrador para obtener más información."
}
}

View File

@@ -12,14 +12,14 @@
"title": "Notification : Utilisateur créé",
"aUserWasCreated": "Un utilisateur a été créé avec ce code {code}.",
"time": "Date",
"notificationNotice": "Note : Les emails de notification peuvent être activés sur le tableau de bord administrateur.",
"notificationNotice": "Note : Les messages de notification peuvent être activés sur le tableau de bord administrateur.",
"name": "Création d'utilisateur"
},
"inviteExpiry": {
"title": "Notification : Invitation expirée",
"inviteExpired": "Invitation expirée.",
"expiredAt": "Le code {code} a expiré à {time}.",
"notificationNotice": "Note : Les emails de notification peuvent être activés sur le tableau de bord administrateur.",
"notificationNotice": "Note : Les messages de notification peuvent être activés sur le tableau de bord administrateur.",
"name": "Expiration de l'invitation"
},
"passwordReset": {

View File

@@ -3,7 +3,7 @@
"name": "Bahasa Indonesia (ID)"
},
"strings": {
"ifItWasNotYou": "Jika ini bukan kamu, silahkan mengabaikan email ini.",
"ifItWasNotYou": "Jika ini bukan kamu, silahkan abaikan email ini.",
"reason": "Alasan",
"helloUser": "Halo {username},"
},

View File

@@ -1,6 +1,6 @@
{
"meta": {
"name": "Italiano (IT)"
"name": "Inglese (US)"
},
"strings": {
"ifItWasNotYou": "Se non sei stato tu, puoi ignorare questa email.",

View File

@@ -11,14 +11,14 @@
"title": "Melding: Gebruiker aangemaakt",
"aUserWasCreated": "Er is een gebruiker aangemaakt door gebruik te maken van code {code}.",
"time": "Tijdstip",
"notificationNotice": "Opmerking: Meldingse-mails kunnen worden aan- of uitgezet via het beheerdersdashboard.",
"notificationNotice": "Opmerking: Meldingen kunnen worden aan- of uitgezet via het beheerders-dashboard.",
"name": "Gebruiker aangemaakt"
},
"inviteExpiry": {
"title": "Melding: Uitnodiging verlopen",
"inviteExpired": "Uitnodiging verlopen.",
"expiredAt": "Code {code} is verlopen op {time}.",
"notificationNotice": "Opmerking: Meldingse-mails kunnen worden aan- of uitgezet via het beheerdersdashboard.",
"notificationNotice": "Opmerking: Meldingen kunnen worden aan- of uitgezet via het beheerders-dashboard.",
"name": "Uitnodiging verlopen"
},
"passwordReset": {

77
lang/email/pl-PL.json Normal file
View File

@@ -0,0 +1,77 @@
{
"meta": {
"name": "Polski (PL)"
},
"strings": {
"ifItWasNotYou": "Jeśli to nie ty, zignoruj tą wiadomość.",
"helloUser": "Hi {username},",
"reason": "Przyczyna"
},
"userCreated": {
"name": "Tworzenie użytkownika",
"title": "Uwaga: Użytkownik utworzony",
"aUserWasCreated": "Użytkownik został utworzony przy użyciu kodu {code}.",
"time": "Czas",
"notificationNotice": "Uwaga: Powiadomienia mogą być włączone z panelu administratora."
},
"inviteExpiry": {
"name": "Zaproszenie wygaśnie",
"title": "Uwaga: Zaproszenie wygasło",
"inviteExpired": "Zaproszenie wygasło.",
"expiredAt": "Kod {code} wygaśnie za {time}.",
"notificationNotice": "Uwaga: Powiadomienia mogą zostać włączone w panelu administratora."
},
"passwordReset": {
"name": "Resetowanie hasła",
"title": "Prośba o reset hasła -Jellyfin",
"someoneHasRequestedReset": "Ktoś niedawno poprosił o zresetowanie hasła w Jellyfin.",
"ifItWasYou": "Jeśli to byłeś ty, wprowadź PIN poniżej.",
"ifItWasYouLink": "Jeśli to byłeś ty, kliknij link poniżej.",
"codeExpiry": "Ten kod wygaśnie {date}, o {time} UTC, dokładnie za {expiresInMinutes}.",
"pin": "PIN"
},
"userDeleted": {
"name": "Usuwanie użytownika",
"title": "Twoje konto zostało usunięte - Jellyfin",
"yourAccountWasDeleted": "Twoje konto Jellyfin zostało usunięte."
},
"userDisabled": {
"name": "Użytkownik wyłączony",
"title": "Twoje konto zostało wyłączone - Jellyfin",
"yourAccountWasDisabled": "Twoje konto zostało wyłączone."
},
"userEnabled": {
"name": "Użytkownik włączony",
"title": "Twoje konto zostało ponownie włączony - Jellyfin",
"yourAccountWasEnabled": "Twoje konto zostało włączone ponownie."
},
"inviteEmail": {
"name": "Email z zaproszeniem",
"title": "Zaproszenie - Jellyfin",
"hello": "Cześć",
"youHaveBeenInvited": "Zostałeś zaproszony na Jellyfin.",
"toJoin": "Aby dołączyć, przejdź używając linku poniżej.",
"inviteExpiry": "To zaproszenie wygaśnie {date} o {time}, czyli za {expiresInMinutes}, więc się pośpiesz.",
"linkButton": "Skonfiguruj swoje konto"
},
"welcomeEmail": {
"name": "Witaj",
"title": "Witaj w Jellyfin",
"welcome": "Witaj w Jellyfin!",
"youCanLoginWith": "Możesz się teraz zalogować używając poniższych danych",
"yourAccountWillExpire": "Twoje konto wygaśnie {date}.",
"jellyfinURL": "URL"
},
"emailConfirmation": {
"name": "Potwierdź adres email",
"title": "Potwierdź adres email - Jellyfin",
"clickBelow": "Naciśnij link poniżej aby potwierdzić adres email.",
"confirmEmail": "Potwierdź adres email"
},
"userExpired": {
"name": "Użytkownik wygasł",
"title": "Twoje konto wygasło - Jellyfin",
"yourAccountHasExpired": "Twoje konto wygasło.",
"contactTheAdmin": "Skontaktuj się z administratorem aby uzyskać więcej szczegółów."
}
}

View File

@@ -11,14 +11,14 @@
"title": "Aviso: Usuário criado",
"aUserWasCreated": "Um usuário foi criado usando o código {code}.",
"time": "Tempo",
"notificationNotice": "Nota: Os emails de notificação podem ser alternados no painel do administrador.",
"notificationNotice": "Nota: As mensagens de notificação podem ser alternadas no painel do administrador.",
"name": "Criação de usuário"
},
"inviteExpiry": {
"title": "Aviso: Convite expirado",
"inviteExpired": "Convite expirado.",
"expiredAt": "O código {code} expirou em {time}.",
"notificationNotice": "Nota: Os emails de notificação podem ser alternados no painel do administrador.",
"notificationNotice": "Nota: As mensagens de notificação podem ser alternadas no painel do administrador.",
"name": "Convite Expirado"
},
"passwordReset": {

77
lang/email/ro-RO.json Normal file
View File

@@ -0,0 +1,77 @@
{
"meta": {
"name": "Română (ROU)"
},
"strings": {
"ifItWasNotYou": "Dacă nu ați fost dvs., vă rugăm să ignorați asta.",
"helloUser": "Salut {username},",
"reason": "Motiv"
},
"userCreated": {
"name": "Crearea utilizatorului",
"title": "Notă: Utilizator creat",
"aUserWasCreated": "Un utilizator a fost creat folosind codul {code}.",
"time": "Oră",
"notificationNotice": "Notă: Mesajele de notificare pot fi comutate în Tabloul de Bord."
},
"inviteExpiry": {
"name": "Expirare invitație",
"title": "Notă: Invitația a expirat",
"inviteExpired": "Invitația a expirat.",
"expiredAt": "Codul {code} a expirat la {time}.",
"notificationNotice": "Notă: Mesajele de notificare pot fi comutate în Tabloul de Bord."
},
"passwordReset": {
"name": "Resetare parolă",
"title": "Solicitată resetarea parolei - Jellyfin",
"someoneHasRequestedReset": "Cineva a cerut recent o resetare a parolei pe Jellyfin.",
"ifItWasYou": "Dacă acesta ați fost dvs., introduceți codul de mai jos în solicitare.",
"ifItWasYouLink": "Dacă acesta ați fost dvs., faceți clic pe linkul de mai jos.",
"codeExpiry": "Codul va expira pe {date}, la {time} UTC, adică în {expiresInMinutes}.",
"pin": "PIN"
},
"userDeleted": {
"name": "Ștergere utilizator",
"title": "Contul dvs. a fost șters - Jellyfin",
"yourAccountWasDeleted": "Contul dvs. Jellyfin a fost șters."
},
"userDisabled": {
"name": "Utilizator dezactivat",
"title": "Contul dvs. a fost dezactivat - Jellyfin",
"yourAccountWasDisabled": "Contul dvs. a fost dezactivat."
},
"userEnabled": {
"name": "Utilizator activat",
"title": "Contul dvs. a fost reactivat - Jellyfin",
"yourAccountWasEnabled": "Contul dvs. a fost reactivat."
},
"inviteEmail": {
"name": "E-mail de invitație",
"title": "Invitație - Jellyfin",
"hello": "Salut",
"youHaveBeenInvited": "Ai fost invitat la Jellyfin.",
"toJoin": "Pentru a vă alătura, urmați linkul de mai jos.",
"inviteExpiry": "Această invitație va expira pe {date} la {time}, care este în {expiresInMinutes}, așa că acționați rapid.",
"linkButton": "Configurați-vă contul"
},
"welcomeEmail": {
"name": "Bun venit",
"title": "Bun venit la Jellyfin",
"welcome": "Bun venit la Jellyfin!",
"youCanLoginWith": "Vă puteți autentifica cu detaliile de mai jos",
"yourAccountWillExpire": "Contul dvs. va expira pe {date}.",
"jellyfinURL": "URL"
},
"emailConfirmation": {
"name": "E-mail de confirmare",
"title": "Confirmați e-mailul dvs. - Jellyfin",
"clickBelow": "Faceți clic pe linkul de mai jos pentru a vă confirma adresa de e-mail și pentru a începe să utilizați Jellyfin.",
"confirmEmail": "Confirmați adresa de e-mail"
},
"userExpired": {
"name": "Expirarea utilizatorului",
"title": "Contul dvs. a expirat - Jellyfin",
"yourAccountHasExpired": "Contul dvs. a expirat.",
"contactTheAdmin": "Contactați administratorul pentru mai multe informații."
}
}

77
lang/email/zh-Hant.json Normal file
View File

@@ -0,0 +1,77 @@
{
"meta": {
"name": "繁體中文 (TW)"
},
"strings": {
"ifItWasNotYou": "如果這不是您,請忽略本信件。",
"helloUser": "你好 {username},",
"reason": "原因"
},
"userCreated": {
"name": "帳戶創建",
"title": "注意:帳戶已創建",
"aUserWasCreated": "已使用邀請碼 {code} 創建了一個帳戶。",
"time": "時間",
"notificationNotice": "注意:可以在管理儀錶板上切換通知訊息。"
},
"inviteExpiry": {
"name": "邀請到期",
"title": "通知:邀請已過期",
"inviteExpired": "邀請已過期。",
"expiredAt": "邀請碼 {code} 在 {time} 過期。",
"notificationNotice": "注意:可以在管理儀錶板上切換通知訊息。"
},
"passwordReset": {
"name": "重置密碼",
"title": "忘記密碼 - Jellfin",
"someoneHasRequestedReset": "最近在 Jellyfin 上有一個您的密碼重置申請。",
"ifItWasYou": "如果是您本人,請在密碼重置提示框中輸入下方的 PIN 碼。",
"ifItWasYouLink": "如果是您本人,請點選下方的連結。",
"codeExpiry": "該 PIN 碼將在 {expiresInMinutes} 内到期UTC 時間 {date} {time})。",
"pin": "PIN 碼"
},
"userDeleted": {
"name": "帳戶刪除",
"title": "您的帳戶已被刪除 - Jellyfin",
"yourAccountWasDeleted": "您的 Jellyfin 帳戶已被刪除。"
},
"userDisabled": {
"name": "帳戶已被禁用",
"title": "您的帳戶已被禁用 - Jellyfin",
"yourAccountWasDisabled": "您的帳戶已被禁用。"
},
"userEnabled": {
"name": "帳戶已被重新啟用",
"title": "您的帳戶已被重新啟用 - Jellyfin",
"yourAccountWasEnabled": "您的帳戶已被重新啟用。"
},
"inviteEmail": {
"name": "邀請電子郵件",
"title": "邀請 - Jellyfin",
"hello": "您好",
"youHaveBeenInvited": "您已受邀註冊 Jellyfin。",
"toJoin": "請點選以下連結來加入。",
"inviteExpiry": "此邀請將於 {date} {time} 到期,即 {expiresInMinutes} 內,請儘快註冊。",
"linkButton": "設置您的帳戶"
},
"welcomeEmail": {
"name": "歡迎",
"title": "歡迎使用 Jellyfin",
"welcome": "歡迎使用 Jellyfin",
"youCanLoginWith": "您可以使用以下資訊登錄",
"yourAccountWillExpire": "您的帳戶將在 {date} 到期。",
"jellyfinURL": "網址"
},
"emailConfirmation": {
"name": "確認電子郵件",
"title": "確認您的電子郵件 - Jellyfin",
"clickBelow": "點選下面的連結以確認您的電子郵件地址並開始使用 Jellyfin。",
"confirmEmail": "確認電子郵件"
},
"userExpired": {
"name": "帳戶到期",
"title": "您的帳號已過期 - Jellyfin",
"yourAccountHasExpired": "您的帳號已過期。",
"contactTheAdmin": "請聯繫管理員瞭解更多資訊。"
}
}

View File

@@ -4,46 +4,46 @@
},
"strings": {
"ifItWasNotYou": "如果这不是您,请忽略本邮件。",
"helloUser": "你好{username}",
"helloUser": "你好 {username}",
"reason": "原因"
},
"userCreated": {
"name": "创建用户",
"title": "注意:用户已创建",
"aUserWasCreated": "使用代码{code}创建了一个用户。",
"name": "用户创建",
"title": "通知:用户已创建",
"aUserWasCreated": "使用邀请码 {code} 创建了一个用户。",
"time": "时间",
"notificationNotice": "注意:通知邮件可以在管理仪表板上进行切换。"
"notificationNotice": "提示:您可以在管理面板上开关邮件通知。"
},
"inviteExpiry": {
"name": "邀请到期",
"title": "注意:邀请已过期",
"title": "通知:邀请已过期",
"inviteExpired": "邀请已过期。",
"expiredAt": "码 {code} 已于 {time} 过期。",
"notificationNotice": "注意:通知邮件可以在管理仪表板上进行切换。"
"expiredAt": "邀请码 {code} 已于 {time} 过期。",
"notificationNotice": "提示:您可以在管理面板上开关邮件通知。"
},
"passwordReset": {
"name": "重设密码",
"title": "要求重设密码 - Jellyfin",
"someoneHasRequestedReset": "最近在Jellyfin上有一个重设密码的请求。",
"ifItWasYou": "如果是您请在提示中输入下面的PIN。",
"ifItWasYouLink": "如果是您,请单击下面的链接。",
"codeExpiry": "该代码将在UTC时间{日期} {时间}到期,也就是{expires In Minutes}。",
"pin": "PIN"
"name": "密码重置",
"title": "忘记密码 - Jellyfin",
"someoneHasRequestedReset": "刚刚在 Jellyfin 上有一个密码重置请求。",
"ifItWasYou": "如果是您本人,请在密码重置提示中输入下面的 PIN。",
"ifItWasYouLink": "如果是您本人,请单击下面的链接。",
"codeExpiry": "该 PIN 码将在 {expiresInMinutes} 内到期UTC 时间 {date} {time}。",
"pin": "PIN"
},
"userDeleted": {
"name": "删除用户",
"title": "您的账已被删除 - Jellyfin",
"yourAccountWasDeleted": "您的 Jellyfin 帐户已被删除。"
"name": "用户删除",
"title": "您的账已被删除 - Jellyfin",
"yourAccountWasDeleted": "您的 Jellyfin 账号已被删除。"
},
"userDisabled": {
"name": "用户禁用",
"title": "您的帐户已被禁用 - Jellyfin",
"yourAccountWasDisabled": "您的帐户已被禁用。"
"title": "您的账号已被禁用 - Jellyfin",
"yourAccountWasDisabled": "您的账号已被禁用。"
},
"userEnabled": {
"name": "用户启用",
"title": "您的帐户已重新启用 - Jellyfin",
"yourAccountWasEnabled": "您的帐户已重新启用。"
"name": "用户重新启用",
"title": "您的账号已被重新启用 - Jellyfin",
"yourAccountWasEnabled": "您的账号已被重新启用。"
},
"inviteEmail": {
"name": "邀请邮件",
@@ -51,15 +51,15 @@
"hello": "您好",
"youHaveBeenInvited": "您已受邀注册 Jellyfin。",
"toJoin": "若要加入,请点击以下链接。",
"inviteExpiry": "此邀请将于 {date} {time} 到期,即 {expires In Minutes},因此请迅速行动。",
"linkButton": "设置您的帐户"
"inviteExpiry": "此邀请将于 {date} {time} 到期,即 {expires In Minutes} 内,请尽快注册。",
"linkButton": "设置您的账号"
},
"welcomeEmail": {
"name": "欢迎",
"title": "欢迎使用Jellyfin",
"welcome": "欢迎使用Jellyfin",
"title": "欢迎使用 Jellyfin",
"welcome": "欢迎使用 Jellyfin",
"youCanLoginWith": "您可以使用以下信息登录",
"yourAccountWillExpire": "您的帐户将于 {date}到期。",
"yourAccountWillExpire": "您的账号将于 {date} 到期。",
"jellyfinURL": "链接"
},
"emailConfirmation": {
@@ -70,8 +70,8 @@
},
"userExpired": {
"name": "用户到期",
"title": "您的帐户已过期 - Jellyfin",
"yourAccountHasExpired": "您的帐户已过期。",
"title": "您的账号已过期 - Jellyfin",
"yourAccountHasExpired": "您的账号已过期。",
"contactTheAdmin": "请联系管理员了解更多信息。"
}
}

60
lang/form/ar-aa.json Normal file
View File

@@ -0,0 +1,60 @@
{
"meta": {
"name": "العربية (AR)"
},
"strings": {
"pageTitle": "أنشاء حساب",
"createAccountHeader": "أنشاء حساب",
"accountDetails": "التفاصيل",
"emailAddress": "البريد الالكتروني",
"username": "اسم المستخدم",
"password": "كلمة المرور",
"reEnterPassword": "تأكيد كلمة المرور",
"reEnterPasswordInvalid": "كلمة المرور غير مطابقة.",
"createAccountButton": "أنشاء حساب",
"passwordRequirementsHeader": "متطلبات كلمة المرور",
"successHeader": "تم!",
"confirmationRequired": "مطلوب تأكيد البريد الإلكتروني",
"confirmationRequiredMessage": "يرجى التحقق من صندوق البريد الإلكتروني الخاص بك للتحقق من عنوانك.",
"yourAccountIsValidUntil": "سيكون حسابك ساري المفعول حتى {date}.",
"sendPIN": "أرسل رقم التعريف الشخصي أدناه إلى الروبوت ، ثم ارجع إلى هنا لربط حسابك.",
"sendPINDiscord": "اكتب {command} في {server_channel} على Discord ، ثم أرسل رقم التعريف الشخصي أدناه.",
"matrixEnterUser": "أدخل معرف المستخدم الخاص بك ، واضغط على إرسال ، وسيتم إرسال رمز PIN إليك. أدخله هنا للمتابعة."
},
"notifications": {
"errorUserExists": "المستخدم موجود مسبقا.",
"errorInvalidCode": "رمز دعوة غير صالح.",
"errorTelegramVerification": "مطلوب التحقق من Telegram.",
"errorDiscordVerification": "مطلوب التحقق من الخلاف.",
"errorMatrixVerification": "مطلوب التحقق من المصفوفة.",
"errorInvalidPIN": "رقم التعريف الشخصي غير صالح.",
"errorUnknown": "خطأ غير معروف.",
"errorNoEmail": "البريد الإلكتروني مطلوب.",
"errorCaptcha": "كلمة التحقق غير صحيحة.",
"errorPassword": "تحقق من متطلبات كلمة المرور.",
"errorNoMatch": "كلمات المرور غير متطابقة.",
"verified": "تم التحقق من الحساب."
},
"validationStrings": {
"length": {
"singular": "يجب أن يتألف من {n} حرف على الأقل",
"plural": "يجب ألا يقل عدد الأحرف عن {n}"
},
"uppercase": {
"singular": "يجب أن تحتوي على {n} حرف كبير على الأقل",
"plural": "يجب ألا يقل عدد الأحرف الكبيرة عن {n}"
},
"lowercase": {
"singular": "يجب أن يتألف من {n} حرف صغير على الأقل",
"plural": "يجب ألا يقل عدد الأحرف الصغيرة عن {n}"
},
"number": {
"singular": "يجب أن يحتوي على {n} رقم على الأقل",
"plural": "يجب أن يحتوي على {n} رقم على الأقل"
},
"special": {
"singular": "يجب أن يتألف من {n} حرف خاص على الأقل",
"plural": "يجب أن يتألف من {n} حرف خاص على الأقل"
}
}
}

View File

@@ -1,9 +1,9 @@
{
"meta": {
"name": "Dansk"
"name": "Dansk (DK)"
},
"strings": {
"pageTitle": "Opret Jellyfin Konto",
"pageTitle": "Opret en Jellyfin Konto",
"createAccountHeader": "Opret Konto",
"accountDetails": "Detaljer",
"emailAddress": "E-mail",
@@ -14,13 +14,12 @@
"createAccountButton": "Opret Konto",
"passwordRequirementsHeader": "Adgangskodekrav",
"successHeader": "Succes!",
"successContinueButton": "Fortsæt",
"confirmationRequired": "E-mail bekræftelse er påkrævet",
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at verificere din adresse.",
"confirmationRequiredMessage": "Tjek venligst din e-mail indbakke for at bekræfte din adresse.",
"yourAccountIsValidUntil": "Din konto er gyldig indtil {date}.",
"sendPIN": "Send nedenstående pinkode til botten, og kom derefter tilbage her for at linke din konto.",
"sendPINDiscord": "Skriv {command} i {server_channel} på Discord, og send PIN-koden nedenfor via. DM til boten.",
"matrixEnterUser": "Skriv dit Bruger ID, tryk Indsend, og en PIN-kode vil blive sendt til dig. Skriv den her efter for at fortsætte."
"sendPIN": "Send nedenstående pinkode til botten, og kom derefter tilbage her for at sammenkoble din konto.",
"sendPINDiscord": "Skriv {command} i {server_channel} på Discord, og send PIN-koden nedenfor.",
"matrixEnterUser": "Skriv dit Bruger ID, tryk Indsend, og en PIN-kode vil blive sendt til dig. Skriv den her efter, for at fortsætte."
},
"notifications": {
"errorUserExists": "Brugeren eksistere allerede.",
@@ -30,7 +29,11 @@
"errorMatrixVerification": "Matrix verifikation påkrævet.",
"errorInvalidPIN": "PIN-koden er ugyldig.",
"errorUnknown": "Ukendt fejl.",
"verified": "konto verificeret."
"verified": "Konto bekræftet.",
"errorNoEmail": "E-mail er påkrævet.",
"errorCaptcha": "Forkert Captcha.",
"errorPassword": "Tjek krav til adgangskode.",
"errorNoMatch": "Adgangskoder stemmer ikke overens."
},
"validationStrings": {
"length": {

View File

@@ -3,7 +3,7 @@
"name": "Deutsch (DE)"
},
"strings": {
"pageTitle": "Jellyfin-Konto erstellen",
"pageTitle": "Jellyfin Konto erstellen",
"createAccountHeader": "Konto erstellen",
"accountDetails": "Kontodaten",
"emailAddress": "E-Mail",
@@ -14,12 +14,11 @@
"createAccountButton": "Konto erstellen",
"passwordRequirementsHeader": "Passwortanforderungen",
"successHeader": "Erfolgreich!",
"successContinueButton": "Weiter",
"confirmationRequired": "E-Mail-Bestätigung erforderlich",
"confirmationRequiredMessage": "Bitte überprüfe dein Posteingang und bestätige deine E-Mail-Adresse.",
"confirmationRequired": "E-Mail Bestätigung erforderlich",
"confirmationRequiredMessage": "Bitte überprüfe deinen Posteingang und bestätige deine E-Mail-Adresse.",
"yourAccountIsValidUntil": "Dein Konto wird bis zum {date} gültig sein.",
"sendPIN": "Sende die untenstehende PIN an den Bot und komm dann hierher zurück, um dein Konto zu verbinden.",
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN als DM an den Bot.",
"sendPINDiscord": "Gib auf Discord {command} in {server_channel} ein und sende die untenstehende PIN.",
"matrixEnterUser": "Gib deine Benutzer-ID ein und drücke auf Absenden. Anschließend erhälst du ein PIN, die hier eingegeben wird um fortzufahren."
},
"validationStrings": {
@@ -53,6 +52,10 @@
"errorDiscordVerification": "Discord-Verifizierung erforderlich.",
"errorMatrixVerification": "Matrix-Verifizierung erforderlich.",
"errorUnknown": "Unbekannter Fehler.",
"verified": "Konto verifiziert."
"verified": "Konto verifiziert.",
"errorNoEmail": "E-Mail Adresse erforderlich.",
"errorCaptcha": "Captcha falsch.",
"errorPassword": "Prüfe die Passwortanforderungen.",
"errorNoMatch": "Passwörter stimmen nicht überein."
}
}

View File

@@ -14,7 +14,6 @@
"createAccountButton": "Δημιουργία Λογαρισμού",
"passwordRequirementsHeader": "Απαιτήσεις Κωδικού",
"successHeader": "Επιτυχία!",
"successContinueButton": "Συνέχεια",
"confirmationRequired": "Απαιτείται επιβεβαίωση Email",
"confirmationRequiredMessage": "Παρακαλώ ελέγξτε το email σας για να επιβεβαιώσετε την διεύθυνση σας .",
"yourAccountIsValidUntil": "Ο λογαριασμός σου θα ισχύει μέχρι {date}."

View File

@@ -1,5 +1,60 @@
{
"meta": {
"name": "English (GB)"
},
"strings": {
"confirmationRequired": "Email confirmation required",
"createAccountHeader": "Create Account",
"accountDetails": "Details",
"emailAddress": "Email",
"username": "Username",
"password": "Password",
"reEnterPassword": "Re-enter Password",
"createAccountButton": "Create Account",
"passwordRequirementsHeader": "Password Requirements",
"successHeader": "Success!",
"pageTitle": "Create Jellyfin Account",
"reEnterPasswordInvalid": "Passwords are not the same.",
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
"sendPIN": "Send the PIN below to the bot, then come back here to link your account.",
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below.",
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue."
},
"notifications": {
"errorUnknown": "Unknown error.",
"verified": "Account verified.",
"errorCaptcha": "Captcha incorrect.",
"errorPassword": "Check password requirements.",
"errorNoMatch": "Passwords don't match.",
"errorTelegramVerification": "Telegram verification required.",
"errorInvalidPIN": "PIN is invalid.",
"errorUserExists": "User already exists.",
"errorInvalidCode": "Invalid invite code.",
"errorMatrixVerification": "Matrix verification required.",
"errorDiscordVerification": "Discord verification required.",
"errorNoEmail": "Email required."
},
"validationStrings": {
"length": {
"singular": "Must have at least {n} character",
"plural": "Must have at least {n} characters"
},
"number": {
"plural": "Must have at least {n} numbers",
"singular": "Must have at least {n} number"
},
"special": {
"plural": "Must have at least {n} special characters",
"singular": "Must have at least {n} special character"
},
"uppercase": {
"singular": "Must have at least {n} uppercase character",
"plural": "Must have at least {n} uppercase characters"
},
"lowercase": {
"singular": "Must have at least {n} lowercase character",
"plural": "Must have at least {n} lowercase characters"
}
}
}

View File

@@ -14,22 +14,27 @@
"createAccountButton": "Create Account",
"passwordRequirementsHeader": "Password Requirements",
"successHeader": "Success!",
"successContinueButton": "Continue",
"confirmationRequired": "Email confirmation required",
"confirmationRequiredMessage": "Please check your email inbox to verify your address.",
"yourAccountIsValidUntil": "Your account will be valid until {date}.",
"sendPIN": "Send the PIN below to the bot, then come back here to link your account.",
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below via DM to the bot.",
"sendPINDiscord": "Type {command} in {server_channel} on Discord, then send the PIN below.",
"matrixEnterUser": "Enter your User ID, press submit, and a PIN will be sent to you. Enter it here to continue."
},
"notifications": {
"errorUserExists": "User already exists.",
"errorInvalidCode": "Invalid invite code.",
"errorAccountLinked": "Account already in use.",
"errorEmailLinked": "Email already in use.",
"errorTelegramVerification": "Telegram verification required.",
"errorDiscordVerification": "Discord verification required.",
"errorMatrixVerification": "Matrix verification required.",
"errorInvalidPIN": "PIN is invalid.",
"errorUnknown": "Unknown error.",
"errorNoEmail": "Email required.",
"errorCaptcha": "Captcha incorrect.",
"errorPassword": "Check password requirements.",
"errorNoMatch": "Passwords don't match.",
"verified": "Account verified."
},
"validationStrings": {

View File

@@ -3,25 +3,37 @@
"name": "Español (ES)"
},
"strings": {
"pageTitle": "Crear cuenta de Jellyfin",
"pageTitle": "Crea una cuenta en Jellyfin",
"createAccountHeader": "Crear una cuenta",
"accountDetails": "Detalles",
"emailAddress": "Correo electrónico",
"username": "Nombre de usuario",
"password": "Contraseña",
"reEnterPassword": "Rescriba su contraseña",
"reEnterPasswordInvalid": "Las contraseñas no son coincidentes.",
"reEnterPassword": "Confirme la contraseña",
"reEnterPasswordInvalid": "Las contraseñas no son similares.",
"createAccountButton": "Crear una cuenta",
"passwordRequirementsHeader": "Requisitos de contraseña",
"successHeader": "¡Éxito!",
"successContinueButton": "Continuar",
"confirmationRequired": "Se requiere confirmación por correo electrónico",
"confirmationRequiredMessage": "Revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}."
"confirmationRequiredMessage": "Por favor, revise la bandeja de entrada de su correo electrónico para verificar su dirección.",
"yourAccountIsValidUntil": "Su cuenta será válida hasta el {date}.",
"sendPINDiscord": "Escribe {command} en {server_channel} en Discord, luego envía el PIN a través de DM al bot.",
"sendPIN": "Envíe el PIN a continuación al bot, luego regrese aquí para vincular su cuenta.",
"matrixEnterUser": "Ingrese su ID de usuario, presione enviar y se le enviará un PIN. Ingrese aquí para continuar."
},
"notifications": {
"errorUserExists": "El usuario ya existe.",
"errorInvalidCode": "Código de invitación no es válido."
"errorInvalidCode": "Código de invitación no válido.",
"errorDiscordVerification": "Se requiere verificación de Discord.",
"errorUnknown": "Error desconocido.",
"verified": "Cuenta verificada.",
"errorTelegramVerification": "Se requiere verificación de Telegram.",
"errorInvalidPIN": "PIN inválido.",
"errorMatrixVerification": "Se requiere verificación de Matrix.",
"errorNoEmail": "Correo electrónico requerido.",
"errorCaptcha": "Captcha Incorrecto.",
"errorPassword": "Requisitos para la contraseña.",
"errorNoMatch": "Las contraseñas no coinciden."
},
"validationStrings": {
"length": {
@@ -29,11 +41,11 @@
"plural": "Debe tener al menos {n} caracteres"
},
"uppercase": {
"singular": "Debe tener al menos {n} caracteres en mayúscula",
"singular": "Debe tener al menos {n} carácter en mayúscula",
"plural": "Debe tener al menos {n} caracteres en mayúscula"
},
"lowercase": {
"singular": "Debe tener al menos {n} caracteres en minúscula",
"singular": "Debe tener al menos {n} carácter en minúscula",
"plural": "Debe tener al menos {n} caracteres en minúscula"
},
"number": {

View File

@@ -14,7 +14,6 @@
"createAccountButton": "ساخت حساب کاربری",
"passwordRequirementsHeader": "کلمه عبور لازم است",
"successHeader": "موفقیت!",
"successContinueButton": "ادامه دادن",
"confirmationRequired": "تایید ایمیل لازم است",
"confirmationRequiredMessage": "لطفاً برای تأیید آدرس خود ، صندوق پستی ایمیل خود را بررسی کنید.",
"yourAccountIsValidUntil": "حساب شما تا {date} معتبر خواهد بود.",

View File

@@ -7,7 +7,7 @@
"pageTitle": "Créer un compte Jellyfin",
"createAccountHeader": "Création du compte",
"accountDetails": "Détails",
"emailAddress": "Email",
"emailAddress": "E-mail",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"reEnterPassword": "Confirmez mot de passe",
@@ -15,7 +15,6 @@
"createAccountButton": "Créer le compte",
"passwordRequirementsHeader": "Mot de passe requis",
"successHeader": "Succès!",
"successContinueButton": "Continuer",
"confirmationRequired": "Confirmation de l'adresse e-mail requise",
"confirmationRequiredMessage": "Veuillez vérifier votre boite de réception pour confirmer votre adresse e-mail.",
"yourAccountIsValidUntil": "Votre compte sera valide jusqu'au {date}.",

60
lang/form/hu-hu.json Normal file
View File

@@ -0,0 +1,60 @@
{
"meta": {
"name": "English (US)"
},
"strings": {
"pageTitle": "Jellyfin fiók létrehozása",
"createAccountHeader": "Fiók létrehozása",
"accountDetails": "Részletek",
"emailAddress": "E-mail",
"username": "Felhasználónév",
"password": "Jelszó",
"reEnterPassword": "Jelszó megerősítése",
"reEnterPasswordInvalid": "A jelszók nem egyeznek",
"createAccountButton": "Fiók létrehozása",
"passwordRequirementsHeader": "Jelszó követelmények",
"successHeader": "Siker!",
"confirmationRequired": "E-mail megerősítés szükséges",
"confirmationRequiredMessage": "Kérjük ellenőrizze az e-mail címére küldött üzenetet, a fiók ellenőrzéséhez.",
"yourAccountIsValidUntil": "A fiókja eddig lesz érvényes: {date}.",
"sendPIN": "Az alábbi kódot küldje el a botnak, majd itt csatolja össze a fiókját.",
"sendPINDiscord": "Discordon haszánlja a {command} parancsot, a {server_channel} csatornában, majd írja be a PIN-t.",
"matrixEnterUser": "Írja be a felhasználója azonosítóját majd nyomja meg a beküldés gombot. A kapott kódot ide írja be."
},
"notifications": {
"errorUserExists": "A felhasználó már létezik.",
"errorInvalidCode": "Érvénytelen meghívó kód.",
"errorTelegramVerification": "Telegram ellenőrzés szükséges.",
"errorDiscordVerification": "Discord ellenőrzés szükséges.",
"errorMatrixVerification": "Matrix ellenőrzés szükséges.",
"errorInvalidPIN": "A PIN-kód érvénytelen.",
"errorUnknown": "Ismeretlen hiba.",
"errorNoEmail": "E-mail szükséges.",
"verified": "Fiók ellenőrizve.",
"errorPassword": "Ellenőrizze a jelszó követelményeket.",
"errorCaptcha": "Hibás Captcha.",
"errorNoMatch": "A jelszavak nem egyeznek."
},
"validationStrings": {
"length": {
"singular": "Legalább {n} karakter",
"plural": "Legalább {n} karakter."
},
"uppercase": {
"singular": "Legalább {n} nagybetűs karakter",
"plural": "Legalább {n} nagybetűs karakter"
},
"lowercase": {
"singular": "Legalább {n} kisbetüs karakter",
"plural": "Legalább {n} kisbetüs karakter"
},
"number": {
"singular": "Tartalmaznia kell legalább {n} számot",
"plural": "Legalább {n} számot kell tartalmazzon"
},
"special": {
"singular": "Legalább {n} speciális karaktert kell tartalmazzon",
"plural": "Legalább {n} speciális karaktert kell tartalmazzon"
}
}
}

View File

@@ -14,13 +14,26 @@
"createAccountButton": "Buat Akun",
"passwordRequirementsHeader": "Persyaratan Kata Sandi",
"successHeader": "Sukses!",
"successContinueButton": "Lanjut",
"confirmationRequired": "Konfirmasi email diperlukan",
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda."
"confirmationRequiredMessage": "Silakan periksa kotak masuk email Anda untuk memverifikasi alamat Anda.",
"sendPINDiscord": "Ketik {command} di {server_channel} pada Discord, lalu kirim PIN di bawah ini.",
"yourAccountIsValidUntil": "Akun kamu berlaku hingga {date}.",
"sendPIN": "Kirim PIN di bawah ini ke bot, lalu kembali kesini untuk menerima link ke akun kamu.",
"matrixEnterUser": "Masukkan User ID kamu, tekan submit, nomor PIN akan dikirimkan ke kamu. Masukkan untuk melanjutkan."
},
"notifications": {
"errorUserExists": "Pengguna sudah ada.",
"errorInvalidCode": "Kode undangan tidak valid."
"errorInvalidCode": "Kode undangan tidak valid.",
"errorDiscordVerification": "Butuh verifikasi pada Discord.",
"errorUnknown": "Terjadi kesalahan.",
"errorTelegramVerification": "Butuh verifikasi pada Telegram.",
"errorInvalidPIN": "Kode PIN salah.",
"errorCaptcha": "Captcha salah.",
"errorPassword": "Perhatikan persyaratan password.",
"errorNoMatch": "Password tidak sama.",
"verified": "Akun terverifikasi.",
"errorNoEmail": "Butuh Email.",
"errorMatrixVerification": "Butuh verifikasi pada Matrix."
},
"validationStrings": {
"length": {

View File

@@ -1,31 +1,35 @@
{
"meta": {
"name": "Italiano (IT)"
"name": "Inglese (US)"
},
"strings": {
"pageTitle": "Crea Un Account Jellyfin",
"createAccountHeader": "Crea Un Account",
"accountDetails": "Dettagli",
"emailAddress": "Email",
"username": "Nome utente",
"username": "Username",
"password": "Password",
"reEnterPassword": "Riscrivi La Password",
"reEnterPasswordInvalid": "Le password non sono uguali.",
"createAccountButton": "Crea Un Account",
"passwordRequirementsHeader": "Requisiti Password",
"successHeader": "Successo!",
"successContinueButton": "Continua",
"confirmationRequired": "Richiesta la conferma Email",
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo."
"confirmationRequiredMessage": "Controlla la tua casella email per verificare il tuo indirizzo.",
"yourAccountIsValidUntil": "Il tuo account sarà valido fino al {date}.",
"sendPIN": "Scrivi il PIN qui sotto al bot, poi torna qui per connettere il tuo account.",
"sendPINDiscord": "Scrivi {command} in {server_channel} su Discord, poi invia il PIN qui sotto.",
"matrixEnterUser": "Inserisci il tuo ID utente, premi invia e ti verrò inviato un PIN. Inseriscilo qui per continuare."
},
"notifications": {
"errorUserExists": "L'utente è già esistente.",
"errorInvalidCode": "Codice di invito non valido."
"errorInvalidCode": "Codice di invito non valido.",
"errorTelegramVerification": "Verifica Telegram richiesta."
},
"validationStrings": {
"length": {
"singular": "Deve avere almeno {n} caratteri",
"plural": "Deve aveere almeno {n} caratteri"
"plural": "Deve avere almeno {n} caratteri"
},
"uppercase": {
"singular": "Deve avere almeno {n} carattere maiuscolo",

View File

@@ -3,7 +3,7 @@
"name": "Nederlands (NL)"
},
"strings": {
"pageTitle": "Maak Jellyfin account aan",
"pageTitle": "Maak Jellyfin-account aan",
"createAccountHeader": "Account aanmaken",
"accountDetails": "Details",
"emailAddress": "E-mail",
@@ -14,13 +14,12 @@
"createAccountButton": "Maak account aan",
"passwordRequirementsHeader": "Wachtwoordvereisten",
"successHeader": "Succes!",
"successContinueButton": "Doorgaan",
"confirmationRequired": "Bevestiging van e-mailadres verplicht",
"confirmationRequiredMessage": "Controleer je e-mail inbox om je adres te bevestigen.",
"yourAccountIsValidUntil": "Je account zal geldig zijn tot {date}.",
"sendPIN": "Stuur onderstaande pincode naar de bot, en kom daarna hier terug om je account te koppelen.",
"matrixEnterUser": "Voer je gebruikers ID in, druk op versturen, en er wordt je een pincode toegestuurd. Vul die hier in om door te gaan.",
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode via DM naar de bot."
"sendPINDiscord": "Typ {command} in {server_channel} op Discord, stuur daarna onderstaande pincode."
},
"validationStrings": {
"length": {
@@ -53,6 +52,10 @@
"errorDiscordVerification": "Discord-verificatie vereist.",
"errorUnknown": "Onbekende fout.",
"errorMatrixVerification": "Matrix-verificatie vereist.",
"verified": "Account geverifieerd."
"verified": "Account geverifieerd.",
"errorNoEmail": "E-mail vereist.",
"errorPassword": "Controleer wachtwoordeisen.",
"errorNoMatch": "Wachtwoorden komen niet overeen.",
"errorCaptcha": "Captcha incorrect."
}
}

60
lang/form/pl-PL.json Normal file
View File

@@ -0,0 +1,60 @@
{
"meta": {
"name": "Polski (PL)"
},
"strings": {
"pageTitle": "Ustawienia - jfa-go",
"createAccountHeader": "Utwórz Konto",
"accountDetails": "Szczegóły",
"emailAddress": "E-mail",
"username": "Nazwa Użytkownika",
"password": "Hasło",
"reEnterPassword": "Powtórz Haslo",
"reEnterPasswordInvalid": "Hasła się różnią.",
"createAccountButton": "Utwórz Konto",
"passwordRequirementsHeader": "Wymagane Hasło",
"successHeader": "Sukces!",
"confirmationRequired": "Potwierdź email",
"confirmationRequiredMessage": "Sprawdź swoją skrzynkę pocztową aby zweryfikować adres.",
"yourAccountIsValidUntil": "Twoje konto pozostanie aktywne do {date}.",
"sendPIN": "Wyślij poniższy kod PIN do bota, a następnie wróć tutaj, aby połączyć swoje konto.",
"sendPINDiscord": "Wpisz {command} w {server_channel} na Discordzie, a następnie wpisz kod PIN poniżej.",
"matrixEnterUser": "Wprowadź swój identyfikator użytkownika, naciśnij Prześlij, a kod PIN zostanie wysłany do Ciebie. Wpisz go tutaj, aby kontynuować."
},
"notifications": {
"errorUserExists": "Nazwa użytkownika zajęta.",
"errorInvalidCode": "Błędny kod zaproszenia.",
"errorTelegramVerification": "Wymagana weryfikacja Telegram.",
"errorDiscordVerification": "Wymagana weryfikacja Discord.",
"errorMatrixVerification": "Wymagana weryfikacja Matrix.",
"errorInvalidPIN": "Błędny kod PIN.",
"errorUnknown": "Nieznany błąd.",
"errorNoEmail": "Wymagany adres email.",
"errorCaptcha": "Błędnie rozwiązana captcha.",
"errorPassword": "Sprawdź wymagania dotyczące hasła.",
"errorNoMatch": "Hasła się różnią.",
"verified": "Konto zweryfikowane."
},
"validationStrings": {
"length": {
"singular": "Musisz użyć przynajmniej {n} znaków",
"plural": "Musisz użyć przynajmniej {n} znak"
},
"uppercase": {
"singular": "Musisz użyć co najmniej {n} wielkich liter",
"plural": "Musisz użyć co najmniej {n} wielkich liter"
},
"lowercase": {
"singular": "Musisz użyć co najmniej {n} małych liter",
"plural": "Musisz użyć co najmniej {n} małych liter"
},
"number": {
"singular": "Musisz użyć co najmniej {n} liczbę",
"plural": "Musisz użyć co najmniej {n} liczb"
},
"special": {
"singular": "Musisz użyć co najmniej {n} znak specjalny",
"plural": "Musisz użyć co najmniej {n} znaków specjalnych"
}
}
}

View File

@@ -6,7 +6,7 @@
"pageTitle": "Criar Conta Jellyfin",
"createAccountHeader": "Criar Conta",
"accountDetails": "Detalhes",
"emailAddress": "Email",
"emailAddress": "E-mail",
"username": "Nome do Usuário",
"password": "Senha",
"reEnterPassword": "Digite a Senha Novamente",
@@ -14,12 +14,11 @@
"createAccountButton": "Criar Conta",
"passwordRequirementsHeader": "Requisitos da Senha",
"successHeader": "Concluído!",
"successContinueButton": "Continuar",
"confirmationRequired": "Confirmação por e-mail",
"confirmationRequiredMessage": "Verifique sua caixa de email para finalizar o cadastro.",
"yourAccountIsValidUntil": "Sua conta é válida até {date}.",
"sendPIN": "Envie o PIN abaixo para o bot e volte aqui para vincular sua conta.",
"sendPINDiscord": "Digite {command} em {server_channel} no Discord e envie o PIN abaixo via DM para o bot.",
"sendPINDiscord": "Digite {command} em {server_channel} no Discord e envie o PIN abaixo.",
"matrixEnterUser": "Digite sua ID de usuário, pressione enviar e um PIN será enviado. E digite aqui para continuar."
},
"notifications": {
@@ -31,7 +30,11 @@
"errorDiscordVerification": "Necessária verificação pelo Discord.",
"errorMatrixVerification": "Necessária verificação Matrix.",
"errorUnknown": "Erro desconhecido.",
"verified": "Conta verificada."
"verified": "Conta verificada.",
"errorNoEmail": "Email necessário.",
"errorCaptcha": "Captcha incorreto.",
"errorPassword": "Verifique os requisitos de senha.",
"errorNoMatch": "As senhas não coincidem."
},
"validationStrings": {
"length": {

Some files were not shown because too many files have changed in this diff Show More