Compare commits

...

1156 Commits
v0.2.9 ... db

Author SHA1 Message Date
Harvey Tindall
e1c215b72e db: remove remaining storage.loadX calls 2023-06-25 20:18:40 +01:00
Harvey Tindall
ea0598e507 db: move legacy data loading out of main/config
put it in loadLegacyData in migrations, which is only called by
migrateToBadger.
2023-06-25 20:17:10 +01:00
Harvey Tindall
28c3d9d2e4 db: use db key to store migration status
the planned config key "migrated_to_db" is not used, instead it is
stored in the database since that's a bit cleaner.
2023-06-25 19:59:11 +01:00
Harvey Tindall
e9f9d9dc98 db: mark migration as completed when it's done
migrated_to_db config key is used. Might also add an extra check to see
if anything is in the DB.
2023-06-25 19:47:31 +01:00
Harvey Tindall
bb75bfd15d db: deprecate customEmails/userPage 2023-06-25 19:40:54 +01:00
Harvey Tindall
9c84fb5887 profiles: fully deprecate old system
ombi_template, configuration, displayprefs, and policy still stuck
around for the admin new user feature. They are now sourced from the
default profile, and eventually a feature to select the source (or no
source) will be added.

this was still used when creating a new user as admin for some reason.
template is now sourced from the default profile.
2023-06-25 18:59:55 +01:00
Harvey Tindall
3bb9272f06 db: mark profile store as deprecated 2023-06-24 21:32:25 +01:00
Harvey Tindall
a735e4ff29 db: migrate user profiles 2023-06-24 21:29:54 +01:00
Harvey Tindall
63948a6de0 db: migrate invites, user expiry
some fixes to stuff in there too, probably
2023-06-24 19:13:05 +01:00
Harvey Tindall
a470d77938 db: fix contact method cleaning daemons
don't think there's a way to negate a query with badgerhold, so i can't
do "delete(not (where JellyfinID in <ExistingUsers>))", and the old
    method of rebuilding the store is no longer possible.
2023-06-24 18:38:52 +01:00
Harvey Tindall
833be688ac storage: start db migration (badger(hold))
migrating to badger, with the badgerhold frontend. So far, done:
* Announcements (small, for a quick test)
* Discord/Telegram/Matrix/Email

most interaction with badgerhold is done through the standard
Get<x>/Get<x>Key/Set<x>Key/Delete<x>Key. UserExists functions have been
added for email and matrix, and those and the original ones now use a
query against the database rather than sifting through every record.
I've tagged these searched fields as "index" for badgerhold, although this
definitely isn't used yet, and i'm not entirely sure if it'll be useful.

migrateToBadger is now in migrations.go, and a temporary config key
"migrated_to_badger" has been added, although it isn't being used yet,
migration is just running every time during development.
2023-06-24 17:05:04 +01:00
Harvey Tindall
fc7ae0ec4e userpage: respect 12h/24h choice 2023-06-24 12:32:28 +01:00
Harvey Tindall
753f5fc517 compile_mjml: use multiprocessing instead of thread 2023-06-24 11:36:15 +01:00
Harvey Tindall
f1b7ef303d Makefile: GOESBUIILD changes
doesn't ever install it if it's already present. Also moved it to
optional dependencies in package.json.
2023-06-23 21:31:33 +01:00
Harvey Tindall
e7d4b5051b build: cleanup reprepro incoming after processing 2023-06-23 14:52:51 +01:00
Harvey Tindall
b7b3aa1eb7 build: fix goreleaser, include optional builder name
builder name shows up in about section again, as does the build time.
2023-06-23 14:41:21 +01:00
Harvey Tindall
f083d6b53f updater: include build date, check against updates
build time is included in the binary, so the buildrone release date is
compared to it when deciding if something is an update or not.
2023-06-23 14:16:36 +01:00
Harvey Tindall
7caa5c5d57 lang: fix the usual on slovenian
someone directly translated "English (US)" again. Why?
2023-06-23 13:49:19 +01:00
Harvey Tindall
65c2722a20 font: switch to hanken grotesk
thought it looked quite nice License included in about section.
2023-06-23 13:45:04 +01:00
Harvey Tindall
6b3fc3d492 lang: correct language names
Low German/Saxon (NDS) is empty entirely, which caused discord lang
registration to error, so i've just filled in the name. Somebody
directly translated "English (US)" into italian instead of putting
Italian in italian, corrected that. Use some common sense!
2023-06-23 13:09:26 +01:00
Harvey Tindall
fec9776def build: fix up goreleaser
removed deprecated options, fixed to work with new user page.
2023-06-23 13:00:46 +01:00
Harvey Tindall
bfeab3648c form: change contact-via radios to checks 2023-06-23 12:30:52 +01:00
Harvey Tindall
c0f2409fcc readme/site: make project status message less pessimistic
I think my current activity on the project justifies the change.
2023-06-23 12:20:18 +01:00
Harvey Tindall
ef5d89f323 Merge "My Account"
User Page/My Account
2023-06-22 22:03:50 +01:00
Harvey Tindall
9bcbffde5d merge lang changes back in 2023-06-22 22:01:37 +01:00
c9rnelius
c37735f2e8 Added translation using Weblate (German (Low)) 2023-06-22 23:00:57 +02:00
c9rnelius
165abc7bea Added translation using Weblate (German (Low)) 2023-06-22 23:00:57 +02:00
Harvey Tindall
7aaafb90e3 form: actually link to the my account page
forgot to do this before. shown on the success modal.
2023-06-22 21:57:19 +01:00
Harvey Tindall
f07c60afb0 userpage: mention link reset requirement 2023-06-22 21:05:54 +01:00
Harvey Tindall
6adbba54ce userpage: invalid refresh token on pw change
user has to log in again, although this is not strictly enforced, as the
standard token remains valid until its expiry.
2023-06-22 20:58:56 +01:00
Harvey Tindall
97db4d714a userpage: implement change password functionality 2023-06-22 20:54:52 +01:00
Harvey Tindall
12ce669566 userpage: add password change card, validation, rearrange page
functionality not done yet, just comitting here because there were lots
of adjustments to layout stuff, accomodating for most combinations of
card presence/size.
2023-06-22 18:51:30 +01:00
Harvey Tindall
4496e1d509 pwr: ensure internal pwr pin is deleted after use 2023-06-22 17:35:34 +01:00
Harvey Tindall
3b3f37365a userpage: autofill username in pwr modal 2023-06-22 12:39:13 +01:00
Harvey Tindall
22c91be127 userpage: make pwr accept username too 2023-06-22 12:39:05 +01:00
Harvey Tindall
3ec3e9672e userpage: time-pad pwr request for ambiguity
the user shouldn't know if the reset has actually been sent (i.e. if an
account with the given contact address exists), so the backend response
is always sent after 1 second.
2023-06-22 12:27:44 +01:00
Harvey Tindall
86daa70ccb userpage: password resets
click "forgot password" on login modal, enter a contact method
address/username, submit and check for a link. Requires link reset to be
enabled.
2023-06-22 12:08:18 +01:00
Harvey Tindall
db97c3b2d4 form: add notice about userpage on success modal, userpage title
uses new strings in the form lang section.
2023-06-22 10:12:22 +01:00
Harvey Tindall
4f298bbc8c userpage: add "back to admin" button 2023-06-22 09:41:41 +01:00
Harvey Tindall
8113f794ab form: fix confirmation success page css 2023-06-21 21:22:05 +01:00
Harvey Tindall
14c18bd668 form: rework email confirmation
realized half the info from the signup form wasnt being stored in the JWT
used to create the account after email confirmation, and instead of
adding them, the -whole request- from the browser is stored temporarily
by the server, indexed by a smaller JWT that only includes the invite
code. Someone complained on reddit about me storing the password in the
JWT a while back, and although security-wise that isn't an issue (only
the server can decrypt the token), it doesn't happen anymore. Happy?
2023-06-21 21:14:41 +01:00
Harvey Tindall
f779f0345e storage: Use familiar api for invite access
An almost identical set of functions to the discord/telegram/matrix
storage ones is now used for accessing invites. No more
parallelism-related issues, yay. Need to do this for everything
eventually.
2023-06-21 20:39:16 +01:00
Harvey Tindall
ebacfd43be form: fix captcha, matrix, telegram
new issue though: discord/telegram/matrix aren't linked when email
confirmation is used!
2023-06-21 20:00:48 +01:00
Harvey Tindall
e4a7172517 messages: assign tokens to jf users on userpage
pins generated on the user page are assigned to that user, no other
jellyifn user can verify them.
2023-06-21 18:26:08 +01:00
Harvey Tindall
3747eaa3a7 messages: refactor dc/tg, fix tg
less external access to Discord/TelegramDaemon internals, will be easier
to keep user/admin-side uses functioning similarly. Also changed their
internal token stores to use a map, and store an expiry. verifiedTokens
is also now a map in telegram. Also fixed issue where token wasn't being
deleted after use on the user page.
2023-06-21 18:02:33 +01:00
Harvey Tindall
761d8d1c03 userpage: refresh pin when contact changed > once 2023-06-21 17:07:02 +01:00
Harvey Tindall
4e7f720214 userpage: hide bg on login, dont refresh page ever 2023-06-21 17:02:57 +01:00
Harvey Tindall
757c3a8aed userpage: move cards around 2023-06-21 13:31:43 +01:00
Harvey Tindall
87b0ae6614 userpage: adjust message row span depending on length 2023-06-21 13:30:09 +01:00
Harvey Tindall
920161b920 settings: add "note" type, shows as card
also comes with a "style" attribute, to apply a color to the aside it's
shown in. Used in User Page/Messages to mention the customize button,
and on User page w/ a critical color to mention the jellyfin login
requirement.
2023-06-21 12:28:52 +01:00
Harvey Tindall
e7f7dcbb78 userpage: show placeholder message card for admins 2023-06-21 11:27:51 +01:00
Harvey Tindall
cc4a97db28 userpage: fix card color in light mode 2023-06-21 11:05:38 +01:00
Harvey Tindall
b546aeb440 userpage: don't wrap contact methods, ellipsise 2023-06-20 22:18:38 +01:00
Harvey Tindall
99679a800d userpage: add customizable message on page 2023-06-20 21:54:55 +01:00
Harvey Tindall
7b9b0d8a84 userpage: implement login message card
Shares code with custom emails, so most related functions have had a
%s/Email/Message/g. Press the edit button on the user page setting to
add a message.
2023-06-20 21:43:25 +01:00
Harvey Tindall
8e153cd92f userpage: unlink accounts 2023-06-20 16:44:12 +01:00
Harvey Tindall
d509abdd5c userpage: add matrix 2023-06-20 13:28:13 +01:00
Harvey Tindall
96c51af15a matrix: modularize 2023-06-20 12:57:52 +01:00
Harvey Tindall
68004e1d34 storage: user set/get methods for contact method access
Get/GetKey/SetKey/DeleteKey methods are used for access to
email/discord/telegram/matrix, everywhere. Mutex added for each, avoids
concurrent read/write issues. Will also make potential transition to
database easier.
2023-06-20 12:19:24 +01:00
Harvey Tindall
fcedea110d telegram: modularize, add to userpage 2023-06-19 22:11:35 +01:00
Harvey Tindall
68aedf07ae discord: pad, underline invite link 2023-06-19 18:03:35 +01:00
Harvey Tindall
094f7cea94 discord: use placeholder if guild icon not available
also centers the invite on the form/user discord modal.
2023-06-19 17:48:24 +01:00
Harvey Tindall
765a749959 discord: modularize user-facing code
will be done for others too, code for discord account linking in form
and userpage is now in ts/modules/account-linking.ts as a configurable
class.
2023-06-19 11:58:09 +01:00
Harvey Tindall
cf7983ca11 userpage: add/edit discord
works identically to on the form, would like to eventually factor out
the discord/telegram/matrix verif stuff so it can be shared between the
two pages though.
2023-06-18 21:38:12 +01:00
Harvey Tindall
609039baeb userpage: change email (+ confirmation)
edit/add button added for email address. Confirmation works too.
2023-06-18 19:38:09 +01:00
Harvey Tindall
03f1a3dbc0 userpage: expand contact card to fill height 2023-06-18 13:04:22 +01:00
Harvey Tindall
75dc9d4d1d userpage: store refresh token separately
stored as "user-refresh" fixes weird issues when two accounts are logged
in.
2023-06-18 12:30:23 +01:00
Harvey Tindall
5beeeb958b userpage: show expiry 2023-06-18 12:27:18 +01:00
Harvey Tindall
a22f032924 userpage: show and allow modification of contact methods 2023-06-17 17:27:44 +01:00
Harvey Tindall
3e034c85d6 auth: provide error message if account is disabled 2023-06-17 13:57:48 +01:00
Harvey Tindall
d3c5feaf1b userpage: use form langfile, move login strings to common
login-related stuff was moved into common using the langmover script, so
that the user page doesn't have to use the admin language files.
2023-06-17 12:48:28 +01:00
Harvey Tindall
96c62f556b langmover: rewrite whole directory when using --extract
--extract now takes a path argument, a new copy of the source folder is
made there. Rebuilding the whole folder gets rid of annoying things like
mis-capitalized files.
2023-06-17 12:45:00 +01:00
Harvey Tindall
ebdad3f7c7 scripts: fix langmover for non-ascii chars 2023-06-16 20:59:06 +01:00
Harvey Tindall
2fc2f1ddb3 lang: add patchable notifications to common 2023-06-16 18:29:49 +01:00
Harvey Tindall
a1af6e3892 scripts: add langmover
a tool to move strings between language file sections. Will be used to
move login strings from admin into their own "login" file section.
2023-06-16 17:27:09 +01:00
Harvey Tindall
726acb9c29 userpage: initial page
login, lang, and theme work. Currently only makes a request to a
hello-world type endpoint to verify auth works. Accessible at
/my/account.
2023-06-16 14:43:37 +01:00
Harvey Tindall
54fde33a20 admin: a little more refactoring
all theme functionality is now in theme.ts, and the tab stuff has been
changed a little but kept in admin as it won't be in use anywhere else
for the time being.
2023-06-16 13:43:34 +01:00
Harvey Tindall
b8cc75c6b4 login: modularize frontend code
all in ts/modules/login.ts
2023-06-15 23:52:16 +01:00
Harvey Tindall
b13fe7f3e4 html: move login modal to own file 2023-06-15 22:00:08 +01:00
Harvey Tindall
81372d6a6b auth: fix "ok" issue
the "ok" returned when the JWT claims are read was being overridden with
"false" before it could be checked.
2023-06-15 21:59:34 +01:00
Harvey Tindall
918f8816c5 auth: slight refactor, setup user auth
user-auth.go contains slightly adjusted versions of auth.go functions,
for authorizing jellyfin users (admin or not). Refactored auth.go so that
most code is shared. User auth isn't hooked up yet, nor has it been
tested.
2023-06-15 21:32:18 +01:00
Harvey Tindall
bf981935cb form: fix header alignment 2023-06-15 18:20:46 +01:00
Harvey Tindall
1fa92f78e4 merge captcha changes 2023-06-15 17:36:17 +01:00
Harvey Tindall
07564bbde3 captcha: recaptcha respects dark mode
also removed the ugly border around it.
2023-06-15 17:35:51 +01:00
Harvey Tindall
4014e93155 signup: add reCAPTCHA
can be enabled in settings > captcha, requires a site key & secret key
from google. New wiki article explains getting these. currently a little
ugly looking on the page itself, hopefully fixable.
2023-06-15 17:11:27 +01:00
Qutyba
f81224a2a6 translation from Weblate (Arabic)
Currently translated at 100.0% (42 of 42 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/ar/
2023-06-14 21:42:07 +02:00
Qutyba
8760152159 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/
2023-06-14 21:42:07 +02:00
Kovács Tamás
5694f30a94 translation from Weblate (Hungarian)
Currently translated at 100.0% (42 of 42 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/hu/
2023-06-14 21:42:07 +02:00
Gabriele Bizzon
156478b381 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (42 of 42 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/pt_BR/
2023-06-14 21:42:07 +02:00
Rafael Gale
ad416b9cb2 translation from Weblate (Spanish)
Currently translated at 100.0% (42 of 42 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/es/
2023-06-14 21:42:07 +02:00
StunBeta
2e39a5e573 Translated using Weblate (French)
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/fr/
2023-06-14 21:42:07 +02:00
StunBeta
cab099d77f Translated using Weblate (French)
Currently translated at 100.0% (112 of 112 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/fr/
2023-06-14 21:42:07 +02:00
StunBeta
0b5e93fd60 Translated using Weblate (French)
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/fr/
2023-06-14 21:42:07 +02:00
StunBeta
6e2ba78204 translation from Weblate (French)
Currently translated at 97.6% (41 of 42 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/form/fr/
2023-06-14 21:42:07 +02:00
Harvey Tindall
115f5ae6a3 Merge accounts sort/filter
Accounts Sort/Filter, UI adjustments
2023-06-14 20:41:27 +01:00
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
Harvey Tindall
5b319d6612 auth: int for refresh token as well 2021-08-22 15:00:20 +01:00
Harvey Tindall
626d623841 auth: don't store jwt expiry as string
caused the jwt library to class all tokens as invalid, now stored as
int64 and converted into a float64 by the library.
2021-08-22 14:13:44 +01:00
Harvey Tindall
08343298fb lowercase lang 2021-08-22 14:13:30 +01:00
Harvey Tindall
7a343bbc08 merge lang 2021-08-22 13:54:44 +01:00
Harvey Tindall
2be34ed063 quote jellyfin URL for censoring in logs 2021-08-22 13:54:39 +01:00
XCQi
7e32c8c4f0 translation from Weblate (Chinese (Simplified))
Currently translated at 43.4% (73 of 168 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/admin/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
a0871e7c47 Translated using Weblate (Chinese (Simplified))
Currently translated at 98.0% (50 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/emails/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
f6834ee63d Translated using Weblate (Chinese (Simplified))
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/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
54b8e30cff Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
504d3e39de Translated using Weblate (Chinese (Simplified))
Currently translated at 95.4% (104 of 109 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
4e2dce7f18 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
b31506d94e translation from Weblate (Chinese (Simplified))
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/zh_Hans/
2021-08-22 14:45:55 +02:00
XCQi
5961bc6086 add translation from Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
b0ffa457df Added translation using Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
9e3fa0ea9a Added translation using Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
ff69d0ba6d Added translation using Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
cf25472746 Added translation using Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
5e651d7b08 Added translation using Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
XCQi
bcd1011b68 add translation from Weblate (Chinese (Simplified)) 2021-08-22 14:45:55 +02:00
vaheeD khoshnouD
f8f0715a3b Translated using Weblate (Persian)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/fa/
2021-08-22 14:45:55 +02:00
vaheeD khoshnouD
e27e08bfd7 Translated using Weblate (Persian)
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/fa/
2021-08-22 14:45:55 +02:00
vaheeD khoshnouD
ae4d47af43 Translated using Weblate (Persian)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/common-strings/fa/
2021-08-22 14:45:54 +02:00
vaheeD khoshnouD
b32271e375 translation from Weblate (Persian)
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/fa/
2021-08-22 14:45:54 +02:00
mLgz0rn
753e233e27 translation from Weblate (Danish)
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/da/
2021-08-22 14:45:54 +02:00
mLgz0rn
d6669bfa70 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/
2021-08-22 14:45:54 +02:00
mLgz0rn
94b288671d 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/
2021-08-22 14:45:54 +02:00
ClankJake
a444e53a99 translation from Weblate (Portuguese (Brazil))
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/pt_BR/
2021-08-22 14:45:54 +02:00
vaheeD khoshnouD
792846f727 Added translation using Weblate (Persian) 2021-08-22 14:45:54 +02:00
vaheeD khoshnouD
6b9fa7bf8a Added translation using Weblate (Persian) 2021-08-22 14:45:54 +02:00
vaheeD khoshnouD
c4378d19db Added translation using Weblate (Persian) 2021-08-22 14:45:54 +02:00
vaheeD khoshnouD
bdebd97dc0 add translation from Weblate (Persian) 2021-08-22 14:45:54 +02:00
Richard de Boer
cf2bd6c095 Translated using Weblate (Dutch)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/password-reset-links/nl/
2021-08-22 14:45:54 +02:00
ClankJake
08f3675f71 translation from Weblate (Portuguese (Brazil))
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/pt_BR/
2021-08-22 14:45:54 +02:00
ClankJake
8d53ddcf93 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (105 of 105 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/pt_BR/
2021-08-22 14:45:54 +02:00
Richard de Boer
c20dc24ccf translation from Weblate (Dutch)
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/nl/
2021-08-22 14:45:54 +02:00
Richard de Boer
b60be0db6d Added translation using Weblate (Dutch) 2021-08-22 14:45:54 +02:00
Harvey Tindall
756e7345cf tray: remove systray dep in non-tray builds 2021-08-18 17:25:16 +01:00
Harvey Tindall
eb3489b34f tray: fix crashlogs, restart from web 2021-08-16 23:01:08 +01:00
Harvey Tindall
9693ce3dcd settings: add redundant URL base for pwr
having it under "Invite Emails" when they weren't in use was confusing.
If only one is set in the config file, it'll be used for both.
2021-07-27 16:53:16 +01:00
Harvey Tindall
6e88c1f4fc setup: MkdirAll config dir if necessary
some have had this issue in the past on new systems/users where ~/.config
doesn't exist yet.
2021-07-27 10:54:52 +01:00
Harvey Tindall
67c60cb677 fix 'jfa-go systemd' layout
lines were printing out-of-order for some reason, so i added time.Sleep
between them.
2021-07-27 10:47:07 +01:00
Harvey Tindall
1a6b0d2b6e upgrade vulnerable deps
upgrade gin and switch dgrijalva/jwt-go to golang-jwt/jwt.
2021-07-27 10:08:01 +01:00
Harvey Tindall
5aaab7904f update contributing
also now hosted on wiki.jfa-go.com.
2021-07-25 01:13:57 +01:00
Harvey Tindall
9885c25a2e email: cleanup; allow re-enabling of custom email without changes 2021-07-24 19:49:08 +01:00
Harvey Tindall
27ad7a4cf7 setup: add descriptive "test connection" messages; disable next button
for #129.
2021-07-24 14:15:10 +01:00
Harvey Tindall
6551eeb938 setup: also respect -host 2021-07-21 17:24:06 +01:00
Harvey Tindall
36c23c1e4f setup: respect -p/-port
fixes #128.
2021-07-21 17:17:59 +01:00
Harvey Tindall
6b4d4da455 accounts: show cog when user known for disabled chatbot 2021-07-20 15:45:00 +01:00
Harvey Tindall
aa2891fc87 matrix: fix instant crash with e2ee
also some more attempts at fixing it, so far all i've found is that if
we don't delete the cryptoStore, the matrix client also gets the same
error we do if it send s a message to us (no session with given ID
found).
2021-07-20 15:34:39 +01:00
Harvey Tindall
db526fc611 exit: improve line number, include stack 2021-07-20 14:57:48 +01:00
Harvey Tindall
a869acd5dc exit: include file/line with error 2021-07-20 14:39:20 +01:00
Harvey Tindall
f763cfb53a announcements: fix [] on {{ .username }} 2021-07-16 20:57:11 +01:00
Harvey Tindall
25a8d3807e announcements: {{ .username }} 2021-07-16 19:46:05 +01:00
Harvey Tindall
b10b558358 announcements: add {username}
also works in the subject.
2021-07-16 19:39:06 +01:00
Harvey Tindall
504b602c0b Merge pull request #127 from hrfee/mautrix
fix external fs; switch to mautrix over gomatrix
2021-07-16 17:17:07 +01:00
Harvey Tindall
f04411e137 matrix: remove crypto dep in main file 2021-07-16 17:11:17 +01:00
Harvey Tindall
1336a87ae2 drone: use custom container for pr builds 2021-07-16 16:50:31 +01:00
Harvey Tindall
872c366384 go mod tidy 2021-07-16 15:51:27 +01:00
Harvey Tindall
762d5325fb matrix: E2EE as build option
since this is so broken and requires CGO deps, E2EE support is now only
included with "make E2EE=on ...". The option to enable will then appear
in settings.
2021-07-16 15:44:14 +01:00
Harvey Tindall
7f37633423 fix external fs 2021-07-16 15:39:22 +01:00
Harvey Tindall
8ec4031ba3 matrix: refactor crypto sections 2021-07-16 14:33:51 +01:00
Harvey Tindall
4c10996c09 matrix: ugly hack to fix encryption after restarts
with a persistent crypto.Store, element reports "** Unable to decrypt:
The secure channel with the sender was corrupted. **", and others
clients just fail. Deleting it before reinitialising the OlmMachine
stops this, although the first message to a user takes a while as i
guess it has re-establish a session (idk, this is above me).
2021-07-14 17:55:26 +01:00
Harvey Tindall
4d3acb2c4c settings: include custom email template setting 2021-07-14 16:48:50 +01:00
Harvey Tindall
833d02b032 matrix: end-to-end encryption by default
Existing chats will remain unencrypted but new ones will be.
2021-07-13 19:02:16 +01:00
Harvey Tindall
30198fab87 matrix: switch to mautrix-go
hopefully this can be used to support end-to-end encryption.
2021-07-13 14:53:33 +01:00
Richard de Boer
51768958c6 Translated using Weblate (Dutch)
Currently translated at 100.0% (105 of 105 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.jfa-go.com/projects/jfa-go/setup/nl/
2021-07-13 15:25:34 +02:00
Harvey Tindall
3e55cd1e31 accounts: add templates for announcements
you can now save announcements as templates, and then use them later by
hovering over the "Announce" button, as well as delete them.
2021-07-10 16:43:27 +01:00
Harvey Tindall
35f0fead53 site: add explanation of release channels 2021-07-09 16:09:23 +01:00
Harvey Tindall
a95d8bff29 site: add syntax highlighting for code 2021-06-30 18:25:51 +01:00
Harvey Tindall
48332a4ffa lowercase lang 2021-06-30 18:10:04 +01:00
Harvey Tindall
2266bbc320 merge translations 2021-06-30 18:06:45 +01:00
Harvey Tindall
b682685a3b update weblate link 2021-06-30 18:06:31 +01:00
mLgz0rn
91411437e2 translation from Weblate (Danish)
Currently translated at 100.0% (163 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
119bed7024 translation from Weblate (Danish)
Currently translated at 52.1% (85 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
6d70a5b24b Translated using Weblate (Danish)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
a99ee04aca Translated using Weblate (Danish)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
3ca2315290 Translated using Weblate (Danish)
Currently translated at 99.0% (100 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
d4bcf229e9 Translated using Weblate (Danish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
3950455a3f Translated using Weblate (Danish)
Currently translated at 94.1% (48 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
7e8e242db0 translation from Weblate (Danish)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
cda90f20af add translation from Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
8ba393ebc0 Added translation using Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
2de1570a98 Translated using Weblate (Danish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
6b01e0d44d Translated using Weblate (Danish)
Currently translated at 94.1% (48 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/da/
2021-06-30 18:57:11 +02:00
mLgz0rn
af4dcd1e2a Added translation using Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
a8ce68959d Added translation using Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
05bc38565c Added translation using Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
574ca4734d Added translation using Weblate (Danish) 2021-06-30 18:57:11 +02:00
mLgz0rn
0957dd58c2 add translation from Weblate (Danish) 2021-06-30 18:57:11 +02:00
thomasl78
4db5d96bb1 Translated using Weblate (French)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/fr/
2021-06-30 18:57:11 +02:00
thomasl78
76c19731cb Translated using Weblate (French)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/fr/
2021-06-30 18:57:11 +02:00
thomasl78
fea368aaae Translated using Weblate (French)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/fr/
2021-06-30 18:57:11 +02:00
thomasl78
1f8bc027c8 translation from Weblate (French)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/fr/
2021-06-30 18:57:11 +02:00
thomasl78
f2240ebf0d translation from Weblate (French)
Currently translated at 99.3% (162 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-06-30 18:57:11 +02:00
thomasl78
9b9f34ae96 Added translation using Weblate (French) 2021-06-30 18:57:11 +02:00
thomasl78
86559d5c76 Added translation using Weblate (French) 2021-06-30 18:57:11 +02:00
Harvey Tindall
40ec5b9933 site: update vulnerable build deps 2021-06-30 14:28:20 +01:00
Harvey Tindall
fbb9f20026 site: fix docker; modal with 'make all' 2021-06-30 01:17:08 +01:00
Harvey Tindall
d5a33cf242 site: fix uncss in make all 2021-06-30 00:48:37 +01:00
Harvey Tindall
c35fdc2cbe site: remove reference to 404 & favicon 2021-06-30 00:38:44 +01:00
Harvey Tindall
84d5bc8f67 site: fix dev dependency 2021-06-30 00:37:31 +01:00
Harvey Tindall
b8d9d22545 add landing page for jfa-go.com; move wiki
located in `site/`. Wiki now @ wiki.jfa-go.com.
2021-06-30 00:32:03 +01:00
Harvey Tindall
788afa1025 config: automatically add http://
for #124, apparently the stdlib needs it.
2021-06-27 01:10:08 +01:00
Harvey Tindall
6ca3ab899c bump mb to 0.3.5 2021-06-26 15:23:39 +01:00
Harvey Tindall
d4096d0062 pwr: trim suffix, not prefix for links 2021-06-24 14:24:08 +01:00
Harvey Tindall
306ede47d6 log: move accidental log message 2021-06-24 02:22:44 +01:00
Harvey Tindall
fc0e86ffd8 change wiki mentions to new location
now at jfa-go.com.
2021-06-23 22:31:11 +01:00
Harvey Tindall
729fc7baf7 Setup: add messages, set password via link
Also changed some section names to use "messages" instead of "emails".
Since I haven't added Discord/Telegram/Matrix bot setup, I mentioned you
can do these later and linked to the setup guides. Sections related to
email mostly now depend on [messages]/enabled now too. Set password via
link has been added to password resets.
2021-06-23 15:44:48 +01:00
Harvey Tindall
2d83e9ff7e add inline to Dockerfile 2021-06-20 20:01:04 +01:00
ClankJake
a0af76364a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (7 of 7 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
169622bf95 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
78b5136b9a Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
e546f50141 translation from Weblate (Portuguese (Brazil))
Currently translated at 99.3% (162 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
e35fe8d425 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
6ed2f7aaa6 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-06-20 19:02:08 +02:00
ClankJake
084e8ec432 Added translation using Weblate (Portuguese (Brazil)) 2021-06-20 19:02:08 +02:00
ClankJake
fd7f74682b Added translation using Weblate (Portuguese (Brazil)) 2021-06-20 19:02:08 +02:00
Harvey Tindall
9950c158a1 move copy button to right, don't uncss when DEBUG=on
keep unused css when DEBUG=on by skipping the uncss step.
2021-06-13 16:31:40 +01:00
Harvey Tindall
21125033ff Add copy button to crash page 2021-06-13 15:36:36 +01:00
Harvey Tindall
1dc0b2234a switch to normal npm mirror in package-lock
was using cnpm as i'd added it to help a user who was having problems
(ended up being internet-related, not blocking).
2021-06-11 23:46:01 +01:00
Harvey Tindall
0ea5c7fdc0 remove inline-css-cli build dep
unnecessary (inline-source-cli already includes its functionality) and a
dependency of it had a high-severity CVE (wouldn't have affected anyone,
but w/e).
2021-06-11 23:39:38 +01:00
Harvey Tindall
b538922c05 Show log on log.Fatal calls, provide "sanitized" version, fix goreleaser
Sanitization means change anything in double quotes to "CENSORED". A
notice is included telling the user to check for themselves as well.
2021-06-11 23:28:21 +01:00
Harvey Tindall
f0f4e8118e Generate crash report txt and webpage
The last 100 lines of logs are now cached, and when a crash occurs, they
are saved to a file in the temp directory ("/tmp" on *nix), and pretty
HTML version is also created and opened in the browser.
* Currently only handles panics, will be included in more places soon
* Copy button and button to generate a GH issue will be added
2021-06-11 21:56:53 +01:00
Harvey Tindall
2f501697db merge translations 2021-06-07 13:58:53 +01:00
Harvey Tindall
0a71d5b216 PWR: Add option to set new password from magic link
For #103. Enable in Settings > Password Resets. Also changes the user's
ombi password.
2021-06-07 13:49:05 +01:00
Harvey Tindall
0014db44f0 form: module-ize password validator 2021-06-05 22:17:10 +01:00
Harvey Tindall
885d2ebf0f admin: fix telegram/discord/matrix enabled varnames 2021-06-05 22:16:57 +01:00
Richard de Boer
6d089a9818 Translated using Weblate (Dutch)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/nl/
2021-06-02 14:39:37 +02:00
Richard de Boer
de81c7e29f Added translation using Weblate (Dutch) 2021-06-02 14:39:37 +02:00
Harvey Tindall
e49996c401 Setup: Don't break if setup not completed, fix restart
Since an invalid example config was created on first run, if the app restarted
before setup was completed, it would crash on the next start. The
example now has a "first_run" flag in it, which is only set to false
when the config is modified. Also fixed restart at the end of setup for
tray builds.
2021-06-01 19:54:13 +01:00
Malte
aa40a72075 Translated using Weblate (German)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Telegram/Matrix/Discord bots
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/chat-bots/de/
2021-06-01 18:29:10 +02:00
Richard de Boer
19b7341e80 Translated using Weblate (Dutch)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/nl/
2021-06-01 18:29:10 +02:00
Malte
73645a7569 Translated using Weblate (German)
Currently translated at 100.0% (20 of 20 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/de/
2021-06-01 18:29:10 +02:00
Malte
a9dac8c04c translation from Weblate (German)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-06-01 18:29:09 +02:00
Malte
43fbbbcd16 translation from Weblate (German)
Currently translated at 100.0% (163 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/de/
2021-06-01 18:29:09 +02:00
Richard de Boer
fc57a8c97f translation from Weblate (Dutch)
Currently translated at 100.0% (37 of 37 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/nl/
2021-06-01 18:29:07 +02:00
Richard de Boer
1fb2ef5675 translation from Weblate (Dutch)
Currently translated at 100.0% (163 of 163 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-06-01 18:29:06 +02:00
Malte
e0d994c35c Added translation using Weblate (German) 2021-06-01 17:21:53 +02:00
hrfee
cab30eb628 Added translation using Weblate (English (United Kingdom)) 2021-06-01 17:13:56 +02:00
Harvey Tindall
71df011556 merge translations 2021-06-01 16:13:28 +01:00
Levi
b2828110e3 Translated using Weblate (Swedish)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/sv/
2021-06-01 17:07:49 +02:00
Levi
50eb05776a translation from Weblate (Swedish)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-06-01 17:07:49 +02:00
Harvey Tindall
19715f25f6 Move all migrations to separate file
Also fixed some inconsistent uses of snake case.
2021-06-01 14:18:49 +01:00
Harvey Tindall
d41a281d53 mention tray icon in issue template 2021-06-01 00:54:22 +01:00
Harvey Tindall
a8229631bd mention matrix in readme 2021-06-01 00:06:35 +01:00
Harvey Tindall
0a2cf6132f merge matrix into local changes 2021-05-31 22:13:52 +01:00
Harvey Tindall
d7ab01063a apt: Use commit count to fix version comparisons
previous fix only worked because the numerical portion of the commit
hash happened to be greater. This should do properly fix it.
2021-05-31 22:11:43 +01:00
Harvey Tindall
6fb8f1ed7f Merge pull request #112 from hrfee/matrix
Matrix Integration
2021-05-31 21:12:57 +01:00
Harvey Tindall
a9b11012bc Matrix: Add example images 2021-05-31 20:56:29 +01:00
Harvey Tindall
e7cb1f516b Mention wiki in Telegram/Discord/Matrix settings descriptions 2021-05-31 20:32:16 +01:00
Harvey Tindall
555d5abf59 bump browserslist version 2021-05-31 18:53:21 +01:00
Harvey Tindall
93937ec3b5 drone: use pre-made image with dependencies
a cron job builds hrfee/jfa-go-build-docker daily, which includes the
apt dependencies for building.
2021-05-31 14:58:20 +01:00
Harvey Tindall
93c7a6e31b apt: Change versioning to hopefully fix updates
0.0.0-{commit} didn't register as updates to the previously installed
  versions, but it looks like 0{commit} works.
2021-05-31 14:13:41 +01:00
Harvey Tindall
a9cabe3d74 apt: also remove jfa-go-tray 2021-05-30 23:37:17 +01:00
Harvey Tindall
d6fd1d6894 apt: remove last unstable before adding
kind of a hack, since all versions have version number 0.0.0, reprepro
skips adding new files.
2021-05-30 23:26:50 +01:00
Harvey Tindall
375022ba95 Matrix: Add token generation wizard
Pressing the "+" next to matrix in settings allows you to enter a
homeserver, username and password to enable matrix and generate an
access token.
2021-05-30 23:09:20 +01:00
Harvey Tindall
75fdf6ec3d Matrix: Connect on accounts tab, customizable chat topic 2021-05-30 11:47:41 +01:00
Harvey Tindall
561c461a18 PWR: TrimPrefix instead of Replace for PWR links 2021-05-30 00:07:18 +01:00
Harvey Tindall
59ebf52fe2 Matrix: Show matrix on accounts page 2021-05-30 00:05:46 +01:00
Harvey Tindall
89fb3fa619 Matrix: Notifications 2021-05-29 21:05:12 +01:00
Harvey Tindall
9bd6abadf4 Matrix: Fix user storage 2021-05-29 19:50:33 +01:00
Harvey Tindall
953a66ec47 Password Resets: Ignore magic link visits from bots
For #108. Literally just searches the useragent for "Bot", seems good
enough for Telegram atleast.
2021-05-29 19:24:00 +01:00
Harvey Tindall
4e826f4167 Matrix: Store user on sign-up 2021-05-29 18:51:43 +01:00
Harvey Tindall
e97b90d4d7 Matrix: Setup bot, add PIN verification
PIN is verified but not used currently. Works a little different than
the others, you input your matrix user ID and then the PIN is sent to
you. The bot doesn't support E2EE, so the bot being the first one to
message ensures the chat is unencrypted.
2021-05-29 17:43:11 +01:00
Harvey Tindall
fb6256d1ed Telegram: Escape all necessary characters
Fixes #108.
2021-05-25 23:03:13 +01:00
Harvey Tindall
7035a3fe9c Tray: Add button to open logs 2021-05-25 20:16:42 +01:00
Harvey Tindall
62c29d55cc Log output to TEMP/jfa-go.log when Tray enabled
-H=windowsgui completely disables Stdout/Stderr on Windows, so logging
is enabled.
2021-05-25 17:59:41 +01:00
Harvey Tindall
a83dbcf3ab debian/ubuntu, not just debian 2021-05-25 01:33:58 +01:00
Harvey Tindall
e48bdcc45b README: change install section layout
Downloads at the top also now link to parts in the install section.
2021-05-24 22:58:11 +01:00
Harvey Tindall
0b473ef01f don't put .debs on buildrone; link to instructions at top of README 2021-05-24 20:30:31 +01:00
Harvey Tindall
e03525a1d1 separate codenames for stable & unstable
templates don't work in name_template as i though, so this should work
instead.
2021-05-24 19:53:53 +01:00
Harvey Tindall
087172c79e fix package naming to avoid conflicts 2021-05-24 18:46:54 +01:00
Harvey Tindall
8fd919bf04 remove chglog, add steps to upload to apt.hrfee.dev
chglog isn't actually needed. Packages are uploaded as jfa-go(-git) and
jfa-go-tray(-git).
2021-05-24 18:37:26 +01:00
Harvey Tindall
2ad84db482 add inaccurate chglog
not really correct, tagged as v0.3.6 despite the few extra commits.
2021-05-24 16:33:20 +01:00
Harvey Tindall
85536ff79e expand CONTRIBUTING, print if tray enabled on startup 2021-05-24 15:58:43 +01:00
Harvey Tindall
8b62c91d13 Mention TrayIcon deps in README 2021-05-23 23:49:48 +01:00
Harvey Tindall
e7d1693517 Enable updater for Tray builds 2021-05-23 23:20:14 +01:00
Harvey Tindall
e78b4882b3 Fix updater for zip files
Forgot to change this when I switched, oops.
2021-05-23 23:05:40 +01:00
Harvey Tindall
e01144950b Mention Discord in README 2021-05-23 22:41:39 +01:00
Harvey Tindall
86ef665b12 Discord: Try to avoid more race conditions
also added RACE=on to Makefile to enable go's race detector.
2021-05-23 22:26:56 +01:00
Harvey Tindall
f419a57e6d Fixed loaded message, Tray by default 2021-05-23 22:12:47 +01:00
Harvey Tindall
d7e8ec95de add missing perms, fix order 2021-05-23 20:48:17 +01:00
Harvey Tindall
5a9bc1c66f Merge Discord branch
Discord Integration, Accounts UI improvements
2021-05-23 20:25:15 +01:00
Harvey Tindall
1f9af8df89 Discord: Add option to provide server invite
When enabled, a temporary one-use invite is created and shown to the
user on the account creation form.
2021-05-23 19:50:03 +01:00
Harvey Tindall
0676b6c41f Discord: Display channel on account creation form 2021-05-23 17:31:20 +01:00
ClankJake
ac842e6273 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-05-23 18:23:21 +02:00
Harvey Tindall
ce8cdced4d Discord: Fix GetUsers, add invite messages
The "Send to" box on the invite tab now accepts username#discriminator,
and a search icon has been added which opens a search window similar to
the one on the accounts tab. DiscordDaemon.GetUsers was also very broken
and wouldn't work with full username#discriminator, that's been fixed.
2021-05-23 16:16:31 +01:00
Harvey Tindall
b8e3fc636c Accounts: Fix cog on telegram when no discord linked
Also, disable telegram & discord if an auth/initialization error occurs.
2021-05-23 14:48:36 +01:00
Harvey Tindall
519a5615cc Accounts: Fix email check on dropdown 2021-05-23 14:35:01 +01:00
Harvey Tindall
168b217553 Discord: fix user links 2021-05-23 14:32:35 +01:00
Harvey Tindall
7d698d63e3 Discord: split discord search into own module
Will also be used for "Send to" on the invite page.
2021-05-23 14:22:18 +01:00
Harvey Tindall
035dbde819 last image 2021-05-23 02:11:48 +01:00
Harvey Tindall
c373d8b2d6 add final oauth tab image 2021-05-23 02:08:02 +01:00
Harvey Tindall
8698c3c6a4 add oauth2 section to bot instructions 2021-05-23 02:03:55 +01:00
Harvey Tindall
0edd2ba68b add settings image for bot setup 2021-05-23 01:51:59 +01:00
Harvey Tindall
b91f0b5a18 Discord: add images for bot creation instructions 2021-05-23 01:38:40 +01:00
Harvey Tindall
24fa841c0d Discord: Wait for non-nil pointer to bot data
While testing others things, I had quite a few nil pointer dereference
errors from accessing bot data right after initializing. A for loop now
waits until the first of the pointers is non-nil, which should
hopefully avoid crashes.
2021-05-23 01:09:03 +01:00
Harvey Tindall
44558b8109 Discord: Remove extra newlines around links
Since links are converted into embeds, links put on their own line often
lead to extra newlines that looks pretty weird. They should now be
stripped.
2021-05-23 01:05:53 +01:00
Harvey Tindall
478b40d0ff Telegram: Escape exclamation marks, remove for images
![alt](link) becomes [alt](link), telegram seems to pick up that they're
images anyway.
2021-05-22 23:38:21 +01:00
Harvey Tindall
8b816dc725 merge translations 2021-05-22 23:26:49 +01:00
Harvey Tindall
81a58f628b Add -H=windowsgui in goreleaser 2021-05-22 23:26:13 +01:00
Harvey Tindall
e98c9b46f1 Accounts: no wrapping for contact dropdown 2021-05-22 23:18:43 +01:00
Harvey Tindall
b3ce7acfcb Accounts: Always inline icons, only one settings cog
Admin chip, email edit bot and contact method cog icon are now always inline.
Only one cog icon is shown now.
2021-05-22 23:05:53 +01:00
Harvey Tindall
9fac79b1f0 Discord: Add users via accounts tab
Doesn't require a PIN like Telegram, as we can access a list of guild
users with the GuildMembers intent set. This has to be enabled under
Bot > Priviliged Gateway intents on the developer portal.
2021-05-22 21:42:15 +01:00
Harvey Tindall
591e3c5ca1 Discord: embed images
![alt](image link) is now converted to an image embed.
2021-05-22 15:32:51 +01:00
Harvey Tindall
35d407afef Discord: remove @ from username 2021-05-22 15:31:25 +01:00
Harvey Tindall
a6447165b7 add email notify enable/disable; remove (de)hyphening
hyphen/dehyphen conflicted with new migration for email contact
preference, and it's been a while since this has been an issue so i've
just commented it out for now.
2021-05-21 22:46:46 +01:00
Malte
23800bb892 Translated using Weblate (German)
Currently translated at 100.0% (7 of 7 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/de/
2021-05-21 23:02:50 +02:00
Malte
b47cb91f55 Translated using Weblate (German)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/de/
2021-05-21 23:02:50 +02:00
Malte
2d9e3fbc1d Translated using Weblate (German)
Currently translated at 100.0% (16 of 16 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/de/
2021-05-21 23:02:50 +02:00
Malte
bf67e27737 Translated using Weblate (German)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/de/
2021-05-21 23:02:50 +02:00
Malte
3427c97e3e translation from Weblate (German)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-05-21 23:02:50 +02:00
Malte
81e69a7166 translation from Weblate (German)
Currently translated at 100.0% (156 of 156 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/de/
2021-05-21 23:02:50 +02:00
Malte
564098b9d8 Added translation using Weblate (German) 2021-05-21 23:02:50 +02:00
ClankJake
ec659174fb translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (156 of 156 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-05-21 23:02:50 +02:00
ClankJake
1a42d8280c Translated using Weblate (Portuguese (Brazil))
Currently translated at 93.7% (15 of 16 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/pt_BR/
2021-05-21 23:02:50 +02:00
ClankJake
b14f10d79d translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-05-21 23:02:50 +02:00
Cornichon420
ee8facd1bf Translated using Weblate (French)
Currently translated at 100.0% (16 of 16 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/fr/
2021-05-21 23:02:50 +02:00
Cornichon420
811657b553 Translated using Weblate (French)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/fr/
2021-05-21 23:02:50 +02:00
Cornichon420
95936f7c29 translation from Weblate (French)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/fr/
2021-05-21 23:02:50 +02:00
Cornichon420
613d4cd9af translation from Weblate (French)
Currently translated at 100.0% (156 of 156 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-05-21 23:02:50 +02:00
Richard de Boer
7beb3d9974 Translated using Weblate (Dutch)
Currently translated at 100.0% (16 of 16 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/nl/
2021-05-21 23:02:50 +02:00
Richard de Boer
6f2bb7f0b5 Translated using Weblate (Dutch)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-05-21 23:02:50 +02:00
Richard de Boer
315b5fda91 translation from Weblate (Dutch)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/nl/
2021-05-21 23:02:50 +02:00
Richard de Boer
a6aa89e502 translation from Weblate (Dutch)
Currently translated at 100.0% (156 of 156 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-05-21 23:02:50 +02:00
Harvey Tindall
3bf722c5fe Discord: send links as embeds
Kind of janky but works. This kind of messes up the layout if you write
links in-line.
2021-05-21 21:59:50 +01:00
Harvey Tindall
e931c09a34 add message when web ui is loaded
a lack of output after "Loading routes" was a little confusing.
2021-05-21 21:39:32 +01:00
Harvey Tindall
f8f5f35cc1 PIN verification, notifications, multiple notif providers
Discord, Email & Telegram can be enabled, although email is always
enabled right now (will fix). Also apparently markdown hyperlinks don't
work in Discord, eventually will implement something to convert them to
embeds.
2021-05-21 21:35:25 +01:00
Harvey Tindall
524941da0c fix heading size with sm 2021-05-21 21:10:32 +01:00
Harvey Tindall
22bba922f9 Discord: Add !lang command 2021-05-18 18:41:42 +01:00
Harvey Tindall
d928df7ab2 Discord: Start bot, add !start and pin validity check
The bot should be created by the admin and added to a discord server
mutual to the intended new user(s). On !start in the server,
communication is moved to DMs. Currently !start works, and validity of a
given PIN is checked although nothing it done with this yet.
2021-05-17 23:42:33 +01:00
Harvey Tindall
4b11bbe21f remove leaked telegram token
token has been revoked, but it doesn't look like it was used anyway.
2021-05-17 11:43:58 +01:00
Harvey Tindall
18bcd55972 remove debug println 2021-05-17 01:32:22 +01:00
Harvey Tindall
057f306ed9 hide/ignore ssl_cert when on windows
x509.SystemCertPool is unavailable on windows, so any value is ignored
and the setting is hidden on the web UI.
2021-05-17 01:16:59 +01:00
Harvey Tindall
76bbb3f147 consistent naming for tray builds 2021-05-16 23:02:09 +01:00
Harvey Tindall
0f3ad8bb69 fix generate_ini for multiline descriptions 2021-05-16 23:00:37 +01:00
Harvey Tindall
1d47b9074f change notray/tray naming, add deb/rpm/apk
Since Tray support requires dependencies, it won't be the default for
releases. deb/rpm/apk support may be broken still.
2021-05-16 22:44:04 +01:00
Harvey Tindall
5167fde080 change tar.gz to zip in drone 2021-05-16 21:12:18 +01:00
Harvey Tindall
a62648ee68 fix cross compilation in goreleaser/drone
Necessary for go-autostart to work on windows. Tray will be enabled by
default for x86_64 windows/linux binaries.
2021-05-16 21:01:31 +01:00
Harvey Tindall
5dee414596 add "autostart on login" option to tray 2021-05-16 17:40:03 +01:00
Harvey Tindall
8cf9b1f905 add basic tray functionality
enable with `make TRAY=on ...`. Cross compilation apparently should work
from linux to linux & windows.
2021-05-16 16:23:28 +01:00
Harvey Tindall
6bf1920160 merge dependabot bump 2021-05-16 15:00:30 +01:00
Harvey Tindall
33f8070e57 cleanup; fix stripping with DEBUG=on 2021-05-16 15:00:13 +01:00
Harvey Tindall
4d2a018032 Merge pull request #98 from hrfee/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.20 to 4.17.21
2021-05-14 14:48:41 +01:00
dependabot[bot]
ca7fb540ee Bump lodash from 4.17.20 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.20 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.20...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 21:24:34 +00:00
ClankJake
beb0712ce9 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/pt_BR/
2021-05-09 20:59:31 +02:00
woosade
a081b14794 Translated using Weblate (Spanish)
Currently translated at 100.0% (7 of 7 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/es/
2021-05-09 20:59:30 +02:00
woosade
e416acf6bd translation from Weblate (Spanish)
Currently translated at 100.0% (152 of 152 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/es/
2021-05-09 20:59:30 +02:00
woosade
bf94f76509 Translated using Weblate (Spanish)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/es/
2021-05-09 20:59:30 +02:00
Richard de Boer
ac239a309c translation from Weblate (Dutch)
Currently translated at 100.0% (152 of 152 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-05-09 20:59:30 +02:00
Richard de Boer
0f12586166 Translated using Weblate (Dutch)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/nl/
2021-05-09 20:59:30 +02:00
Cornichon420
b1b50ce561 Translated using Weblate (French)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/fr/
2021-05-09 20:59:30 +02:00
Cornichon420
8e2bf48ab4 Translated using Weblate (French)
Currently translated at 100.0% (13 of 13 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/fr/
2021-05-09 20:59:30 +02:00
Cornichon420
6ec5022a0d translation from Weblate (French)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/fr/
2021-05-09 20:59:30 +02:00
Cornichon420
ef97e0ac76 translation from Weblate (French)
Currently translated at 100.0% (152 of 152 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-05-09 20:59:30 +02:00
Harvey Tindall
30736a055d add example bot settings for wiki 2021-05-08 16:37:40 +01:00
Harvey Tindall
d0905a29be add example bot creation for wiki 2021-05-08 16:29:16 +01:00
Harvey Tindall
fe5cf69b7a Merge Telegram support
For #94.
2021-05-08 16:15:41 +01:00
Harvey Tindall
c560ec0f9f Merge branch 'main' into telegram 2021-05-08 16:08:20 +01:00
Harvey Tindall
71554e0c85 Telegram: Change user's contact method in accounts
By clicking the cog next to the telegram username, one can select
whether to contact through telegram or email.
2021-05-08 15:53:42 +01:00
Harvey Tindall
0efd7c5718 Telegram: add language files
somehow these were included in the .gitignore.
2021-05-07 23:45:53 +01:00
Harvey Tindall
901ad7529e mention wiki in telegram settings description 2021-05-07 23:36:46 +01:00
Harvey Tindall
b64bcc9738 include telegram verif in images 2021-05-07 23:30:32 +01:00
Harvey Tindall
fddb7b7584 Mention telegram in readme 2021-05-07 23:18:44 +01:00
Harvey Tindall
b91302ddf8 Invite: fix "none yet" message on users created 2021-05-07 22:41:51 +01:00
Harvey Tindall
ea0293bd4e Split some settings into new "messages" section
Most email dependant sections now depend on this. Also renamed more
email things.
2021-05-07 21:53:29 +01:00
Harvey Tindall
51f2f4cc6a Telegram: close updates channel on restart
Also removed some references to email.
2021-05-07 18:29:56 +01:00
Harvey Tindall
2d93b3b7ee Telegram: Allow admin to add telegram contact
Works in the same way as on the form, but can now be done in the
accounts tab.
2021-05-07 18:20:35 +01:00
Harvey Tindall
0f41d1e6cf Telegram: Display username on accounts tab 2021-05-07 17:01:22 +01:00
Harvey Tindall
36edd4ab0d Telegram: Use markdown for custom emails/announcements
Had no idea telegram supported this, pretty cool.
2021-05-07 16:33:44 +01:00
Harvey Tindall
716d6a931a Telegram: Send messages via telegram
Most messages are now sent as plaintext via telegram when suitable.
2021-05-07 16:06:47 +01:00
Harvey Tindall
72bf280e2d telegram: Fix UI and store useful Telegram info
Creation now works, and language preferences made before signup are
kept. telegram file storage now uses the Jellyfin ID as a key, which
makes much more sense. Also added radios to select preferred notification
method (email/telegram) as well, which the admin will soon be able to
change also.
2021-05-07 14:32:51 +01:00
Harvey Tindall
326c2cf70a modal: use arrow function to avoid 'this' naming collision 2021-05-07 14:30:30 +01:00
Harvey Tindall
2816c6277d modal: add onopen/onclose 2021-05-07 13:22:07 +01:00
Harvey Tindall
99875b9176 almost complete telegram user verification
When signing up, the user is given a pin code which they send to a
telegram bot. This provides user verification, but more importantly
allows the bot to message the user, as the Telegram API requires the
user to interact with the bot before it can do the opposite.

The bot should recognize the correct language, but a /lang command is
also provided to change it.

The verification process is pretty much functional but ui is still
broken, and it isn't properly integrated yet.
2021-05-07 01:08:12 +01:00
Harvey Tindall
0e21942cd6 add hard restart for updates on *nix
reincarnates app.Restart() removed in
bbb0568cc4 as app.HardRestart().
2021-05-03 20:08:23 +01:00
Harvey Tindall
b2b5083102 fix checkCheckCount on accounts reload 2021-05-03 18:55:46 +01:00
Harvey Tindall
c0f316d049 add preview to Announcements 2021-05-03 18:35:27 +01:00
Harvey Tindall
2c6d08319b add typechecking step to Makefile when DEBUG=on 2021-05-03 18:32:56 +01:00
Harvey Tindall
5d8f139356 fix race condition; rename route functions; fix swagger params
fix race condition when notifying of invite expiry, rename custom email
related functions as to reduce confusion, and add proper path params for
some swagger routes. Also moved some stuff around in api.go.
2021-05-02 20:42:37 +01:00
Harvey Tindall
87ef71b415 lowercase lang 2021-05-02 15:25:09 +01:00
André Cruz
cf99ae880c Translated using Weblate (Spanish)
Currently translated at 100.0% (6 of 6 strings)

Translation: jfa-go/Password Reset Links
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/password-reset-links/es/
2021-05-02 16:23:10 +02:00
André Cruz
8e86078394 Translated using Weblate (Spanish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/es/
2021-05-02 16:23:10 +02:00
André Cruz
beea903879 translation from Weblate (Spanish)
Currently translated at 100.0% (151 of 151 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/es/
2021-05-02 16:23:10 +02:00
André Cruz
c5e4c5d509 Translated using Weblate (Spanish)
Currently translated at 100.0% (13 of 13 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/es/
2021-05-02 16:23:10 +02:00
André Cruz
fac951c733 Translated using Weblate (Spanish)
Currently translated at 100.0% (98 of 98 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/es/
2021-05-02 16:23:10 +02:00
Cornichon420
83449f3332 Translated using Weblate (French)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/fr/
2021-05-02 16:23:10 +02:00
Cornichon420
2a9fc8c7a5 translation from Weblate (French)
Currently translated at 88.0% (133 of 151 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-05-02 16:23:10 +02:00
André Cruz
f8d4f79271 Translated using Weblate (Spanish)
Currently translated at 100.0% (98 of 98 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/es/
2021-05-02 16:23:10 +02:00
André Cruz
bc466d0c6f Added translation using Weblate (Spanish) 2021-05-02 16:23:10 +02:00
Harvey Tindall
382a0f4c3c add donate button to about 2021-05-02 15:16:28 +01:00
Harvey Tindall
488c2f5df5 fix broken url in welcome email 2021-05-02 14:44:19 +01:00
Harvey Tindall
43effd0c32 add reset link option to setup 2021-05-02 14:15:03 +01:00
Harvey Tindall
af61549bf1 ombi: reset password when using pwr links
When password reset links are enabled, the ombi password will be reset
to the PIN along with Jellyfin.
2021-05-02 13:23:59 +01:00
Harvey Tindall
22a0d8925d Remove unused typescript, update config readme 2021-05-02 13:23:33 +01:00
Harvey Tindall
59a014f681 fix title for invite emails 2021-05-02 12:50:04 +01:00
Harvey Tindall
9944cc2db9 refactor; move logger to module 2021-05-01 00:13:57 +01:00
Harvey Tindall
570e3a1e54 fix en-es name and filename 2021-04-30 13:54:53 +01:00
woosade
a9bde40661 translation from Weblate (Spanish)
Currently translated at 100.0% (151 of 151 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/es/
2021-04-29 23:54:04 +02:00
woosade
b03a185e88 Translated using Weblate (Spanish)
Currently translated at 100.0% (98 of 98 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/es/
2021-04-29 23:54:03 +02:00
woosade
e450587eea translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (151 of 151 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-04-29 23:54:03 +02:00
Richard de Boer
30a529baac translation from Weblate (Dutch)
Currently translated at 100.0% (151 of 151 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-04-29 23:54:03 +02:00
woosade
adbb74f56b Translated using Weblate (Spanish)
Currently translated at 100.0% (98 of 98 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/es/
2021-04-28 15:15:40 +02:00
woosade
223b4df172 translation from Weblate (Spanish)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/es/
2021-04-28 15:15:40 +02:00
woosade
44dc315914 Translated using Weblate (Spanish)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/es/
2021-04-28 15:15:40 +02:00
woosade
c959e2ce4d translation from Weblate (Spanish)
Currently translated at 100.0% (150 of 150 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/es/
2021-04-28 15:15:40 +02:00
woosade
57b10dd514 Translated using Weblate (Spanish)
Currently translated at 100.0% (13 of 13 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/es/
2021-04-28 15:15:40 +02:00
woosade
9da0f89613 Added translation using Weblate (Spanish) 2021-04-28 15:15:40 +02:00
woosade
4104cb334e add translation from Weblate (Spanish) 2021-04-28 15:15:40 +02:00
woosade
94067a1ec2 Added translation using Weblate (Spanish) 2021-04-28 15:15:40 +02:00
woosade
3e9da3baf7 add translation from Weblate (Spanish) 2021-04-28 15:15:40 +02:00
woosade
6129305b2c Added translation using Weblate (Spanish) 2021-04-28 15:15:40 +02:00
ClankJake
7165eb1f59 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-04-28 15:15:40 +02:00
ClankJake
a4820de423 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (150 of 150 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-04-28 15:15:40 +02:00
Marketos Damigos
0c09f3b05f Translated using Weblate (Greek)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/el/
2021-04-28 15:15:40 +02:00
Marketos Damigos
269d67f071 translation from Weblate (Greek)
Currently translated at 100.0% (150 of 150 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/el/
2021-04-28 15:15:39 +02:00
Richard de Boer
bdc0c0ffa2 Translated using Weblate (Dutch)
Currently translated at 100.0% (51 of 51 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-04-28 15:15:39 +02:00
Richard de Boer
c00f5f4330 translation from Weblate (Dutch)
Currently translated at 100.0% (150 of 150 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-04-28 15:15:39 +02:00
Harvey Tindall
a2c344de83 add shorthand flag names
along with an ugly wrapper for the help message that merges the
descriptions for the short & long versions.
2021-04-24 23:54:56 +01:00
Harvey Tindall
886ae64feb add "systemd" command to generate a .service file
never got around to adding this from jellyfin-accounts for some reason.
2021-04-24 18:54:31 +01:00
Harvey Tindall
90a2c1f2e7 Fix email editor for other email types 2021-04-22 19:16:41 +01:00
Harvey Tindall
d772e43e44 merge language changes 2021-04-15 15:34:52 +01:00
Harvey Tindall
8fdab39b18 use templateEmail and show conditionals in editor 2021-04-15 15:34:17 +01:00
Harvey Tindall
f7d2771263 add email templater with basic if statements
at this point I really should've just used text/template, but I guess
this way compatibility is kept with existing custom emails. If statement
works as so:

{if variable}variable was true{endif}
{if !variable}variable was false{endif}

no else yet, just do as above (two if statements).
2021-04-14 23:58:54 +01:00
ClankJake
e8b1cca9ca translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-04-14 19:15:43 +02:00
Richard de Boer
d4d7219801 translation from Weblate (Dutch)
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-04-14 19:15:43 +02:00
Harvey Tindall
3273607fc3 translation: add fallback option to langMeta
If set to a language code (e.g fr-fr), any missing strings will be
filled in from that language (if possible) rather than from the default
en-us. Currently not used, but could be useful in the future for
variations of the same language.
2021-04-13 18:34:13 +01:00
Harvey Tindall
55e21f8be3 accounts: add user enable/disable & emails 2021-04-12 21:28:36 +01:00
ClankJake
dafb439a7d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (45 of 45 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-04-12 18:27:42 +02:00
Richard de Boer
ab94de2f95 Translated using Weblate (Dutch)
Currently translated at 100.0% (45 of 45 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-04-12 18:27:42 +02:00
Harvey Tindall
3dc0df0ac2 fix user expiry when only month field set 2021-04-09 13:35:46 +01:00
Harvey Tindall
d701c5f27d add months field to invites & expiry 2021-04-08 20:43:01 +01:00
Harvey Tindall
a8f71c83da store language preference as cookie 2021-04-08 16:03:46 +01:00
Harvey Tindall
7a3e0d60f9 add expiry to welcome email, add dummy emailer for debugging
the "yourAccountWillExpire" has also been added to the editor for #81.
To use the dummy emailer, set [email]/method to "dummy".
2021-04-08 14:20:13 +01:00
Harvey Tindall
2687af31ca updater: immediately store executable
for some reason I kept the response body and downloaded file in memory,
which led to timeouts and failed updates.
2021-04-07 18:17:18 +01:00
Harvey Tindall
d51a6abb02 remove cl.md 2021-04-07 17:45:31 +01:00
Harvey Tindall
374ffbf01f fix incomplete lang patching, add en-gb stub
en-gb is empty, so it's patched with en-us strings. Added so DD/MM/YY
date formatting was possible in the ui.
2021-04-07 17:42:15 +01:00
Harvey Tindall
871bc9f396 use proper date formatting on form for expiry 2021-04-07 15:17:15 +01:00
Harvey Tindall
66b7df7cde use selected language for time format, add manual selector
You can now choose between 12h and 24h time in the top left language
menu. Your preference is stored by the browser for future visits.
2021-04-07 15:09:44 +01:00
Harvey Tindall
bc76770ca4 move 12h/24h time strings to common 2021-04-07 15:09:25 +01:00
Harvey Tindall
7196361cf6 (hopefully) get proper locale from browser 2021-04-07 14:05:17 +01:00
Harvey Tindall
3e73d16cce merge language changes 2021-04-06 21:30:14 +01:00
Harvey Tindall
3f8414c70a use unix timestamp for inv created & usedBy
usedBy is still stored as a string in invites.json to cope with existing
invites with times stored formatted. knz/strtime requires cgo for
strptime, so it has been replaced with the native itchyny/timefmt-go.
2021-04-06 21:25:44 +01:00
Harvey Tindall
6ec2186bdf switch accounts tab to unix times
should now respect the client's locale.
2021-04-06 20:53:30 +01:00
ClankJake
6dd575b276 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (44 of 44 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
1a98946d71 Translated using Weblate (German)
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/de/
2021-04-06 19:49:18 +02:00
ClankJake
8922549bdb Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/pt_BR/
2021-04-06 19:49:18 +02:00
Richard de Boer
173b49aeb7 Translated using Weblate (Dutch)
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/nl/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
eee6046465 Translated using Weblate (German)
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/de/
2021-04-06 19:49:18 +02:00
JoshiJoshiJoshi
b76011be4f translation from Weblate (German)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-04-06 19:49:18 +02:00
Richard de Boer
3d93d79b0b Translated using Weblate (Dutch)
Currently translated at 100.0% (44 of 44 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-04-06 19:49:18 +02:00
Harvey Tindall
7dcc9b20a1 clear user cache when user expires 2021-04-06 18:39:12 +01:00
Harvey Tindall
754b956206 remove extra logs 2021-04-06 18:32:32 +01:00
Harvey Tindall
47ac505cac shutdown your background workers!
I believe everything #74 was caused by not shutting down the userDaemon
when we do a pseudo-restart. shutdown of it and the invite daemon are
now deferred so this should fix any problems and reduce log spam.
2021-04-06 18:12:06 +01:00
Harvey Tindall
e6e5231f63 add extra logging 2021-04-06 18:02:15 +01:00
Harvey Tindall
78049d4a33 hyphenate/dehyphenate users.json if necessary
doubt this would have caused problems anyway but why not.
2021-04-06 15:46:28 +01:00
Harvey Tindall
8a6cfe0b4d disallow negative values in ExtendExpiry, fix nil map err 2021-04-06 14:00:32 +01:00
Harvey Tindall
afedc78113 only load users if they don't exist already
another guess for #77.
2021-04-06 13:53:07 +01:00
Harvey Tindall
76b822213e add more error logging; mutex for app.storage.users 2021-04-06 13:44:52 +01:00
Harvey Tindall
ab3d5f3321 fix logging for expiry extension
also delete expiries for users that no longer exist.
2021-04-06 13:31:42 +01:00
Harvey Tindall
e1d42c8a87 Update CONTRIBUTING.md, mb 0.3.3
One last missing field added for #76.
2021-04-05 16:34:47 +01:00
Harvey Tindall
f53c852a4d bump mb to v0.3.2
includes missing struct fields for user Policy, fixes #76.
2021-04-05 15:07:30 +01:00
Harvey Tindall
aaea889e47 use apt-get in drone.yml 2021-04-03 21:38:26 +01:00
Harvey Tindall
bf98c74ecf Merge pull request #75 from Toucan-Sam/patch-1
Fix docker link in README.md
2021-04-03 21:37:54 +01:00
Toucan-Sam
fcadabd339 Fix docker link in README.md 2021-04-04 08:32:38 +12:00
Harvey Tindall
2a0edeb3c5 bump mediabrowser version, more consistent logs
uses descriptive errors added in mb v0.2.0. Also improved
the consistency of logs in api.go/main.go.
2021-04-02 22:13:04 +01:00
Harvey Tindall
30f16e7207 email: use strconv.Itoa instead of sprintf 2021-04-02 15:56:34 +01:00
Harvey Tindall
dbe7e2e659 remove ts-debug 2021-04-01 14:33:57 +01:00
Harvey Tindall
e16f05b130 use build constraints for embed, clean up makefile
internal-files/external-files and compile-debug are gone, the
environment variables INTERNAL=on/off and DEBUG=on/off replace them.
2021-04-01 14:22:11 +01:00
Harvey Tindall
07573a515a merge translation 2021-04-01 12:58:06 +01:00
Harvey Tindall
b3a2de50cf hide no_username support message on setup
fixes #74.
2021-04-01 12:56:47 +01:00
Marketos Damigos
5388d3d4c0 Translated using Weblate (Greek)
Currently translated at 100.0% (11 of 11 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/el/
2021-03-31 21:18:01 +02:00
Marketos Damigos
c392d48174 Translated using Weblate (Greek)
Currently translated at 100.0% (44 of 44 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/el/
2021-03-31 21:18:00 +02:00
Marketos Damigos
967fab3411 Translated using Weblate (Greek)
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/el/
2021-03-31 14:54:34 +02:00
Marketos Damigos
d7845b78f6 Translated using Weblate (Greek)
Currently translated at 100.0% (43 of 43 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/el/
2021-03-31 14:54:34 +02:00
Marketos Damigos
a253858625 translation from Weblate (Greek)
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/el/
2021-03-31 14:54:34 +02:00
Marketos Damigos
ad1aae16e3 translation from Weblate (Greek)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/el/
2021-03-31 14:54:34 +02:00
Harvey Tindall
9370913ace add password reset link option
When enabled (in Settings > Password Resets), a magic link will be sent
instead of a PIN when the user tries reset their password. By doing
this the user doesn't have to keep the Jellyfin tab open to enter the
code.
2021-03-30 22:41:28 +01:00
Harvey Tindall
dcd2e234e8 move "copy" string to common, add "copied"
for a new password reset feature.
2021-03-30 21:16:24 +01:00
Harvey Tindall
762dac2581 move mediabrowser to separate repo 2021-03-29 21:49:46 +01:00
Harvey Tindall
1cf8d3037b remove dependency on common from mediabrowser 2021-03-29 20:57:13 +01:00
Harvey Tindall
40808bdcb9 merge language changes 2021-03-29 20:54:06 +01:00
Harvey Tindall
2451d69341 rewrite lang.go format and templateString
surprisingly not much faster than the originals.
2021-03-27 16:07:22 +00:00
virusperfect
e449853568 Translated using Weblate (German)
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/de/
2021-03-27 00:19:06 +01:00
virusperfect
2082e960c2 Translated using Weblate (German)
Currently translated at 100.0% (43 of 43 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/de/
2021-03-27 00:19:06 +01:00
virusperfect
7b2a083f98 translation from Weblate (German)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/de/
2021-03-27 00:19:06 +01:00
virusperfect
270143a8f6 translation from Weblate (German)
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/de/
2021-03-27 00:19:06 +01:00
ClankJake
766b69d95e translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-03-27 00:19:06 +01:00
ClankJake
f5addc4947 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/pt_BR/
2021-03-27 00:19:06 +01:00
ClankJake
55eb59c526 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-03-27 00:19:06 +01:00
Richard de Boer
679cac4dbd Translated using Weblate (Dutch)
Currently translated at 100.0% (100 of 100 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/nl/
2021-03-27 00:19:06 +01:00
Harvey Tindall
a0a25d64f1 rewrite stripmd, fix some typos
doesn't work any better, but more efficient and doesn't require
eyebleach after viewing.
2021-03-26 23:13:19 +00:00
Harvey Tindall
9875458b01 rewrite time unmarshaler for mediabrowser
Last ditch effort for #69, removes quotes and trailing Z's manually and
also removes nanoseconds since they're useless.
2021-03-23 21:59:41 +00:00
Harvey Tindall
f0dccc58aa separate pprof from debug mode
enabled with -pprof now.
2021-03-23 21:59:04 +00:00
Harvey Tindall
636bc22d52 reimplement Lshortfile for log wrapper
Fixes all debug messages having "logger:<line>:" instead of the actual
caller.
2021-03-23 21:57:53 +00:00
Harvey Tindall
fc6b6a9c6b Fix time parser for "ZZ" prefix
I think this means UTC-08:00, but this just strips it since time
handling is pretty naïve already.
2021-03-23 16:10:25 +00:00
Harvey Tindall
1a6d78352c add comments, fix user expiry log spam
now actually removes the already deleted user from the expiry list.
2021-03-21 22:50:33 +00:00
Harvey Tindall
e351c35cc8 use banner class on banner in about 2021-03-21 00:59:51 +00:00
Harvey Tindall
618cc32a17 hide updates from settings when disabled at build-time 2021-03-20 23:32:32 +00:00
Harvey Tindall
a8bf670697 dont log updates when disabled 2021-03-20 23:20:07 +00:00
Harvey Tindall
0bdf8ad6ce put upload.py in parent dir 2021-03-20 23:16:54 +00:00
Harvey Tindall
8f65e2e968 fix drone.yml for stable docker 2021-03-20 23:13:03 +00:00
Harvey Tindall
0d3f96c3a7 fix button height on accounts tab & expiry types on mobile 2021-03-20 22:16:24 +00:00
Harvey Tindall
cfa7947020 wrap items in accounts header
fixes mobile layout.
2021-03-20 19:23:54 +00:00
Harvey Tindall
b91de3f319 update images and readme 2021-03-20 19:04:26 +00:00
Harvey Tindall
1704ae8cb1 fix language link color on dark theme 2021-03-20 18:24:35 +00:00
Richard de Boer
50c6e6031d translation from Weblate (Dutch)
Currently translated at 100.0% (140 of 140 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-03-20 19:02:09 +01:00
Harvey Tindall
de92516d52 add updates section to setup 2021-03-20 18:00:01 +00:00
Harvey Tindall
cd67d3e7ab merge translation 2021-03-18 16:49:17 +00:00
Harvey Tindall
c556878f11 hide password resets on setup when emby selected 2021-03-18 16:47:13 +00:00
Richard de Boer
3af4607171 translation from Weblate (Dutch)
Currently translated at 100.0% (139 of 139 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-03-15 23:52:09 +01:00
Harvey Tindall
111533fa2d add advanced setting type with toggle in settings 2021-03-15 22:51:17 +00:00
Harvey Tindall
5dc0a68b44 merge translations 2021-03-15 21:58:36 +00:00
Harvey Tindall
43e5bbbe21 add option to trust specific cert for SMTP 2021-03-15 21:57:42 +00:00
ClankJake
42921f6a3e translation from Weblate (Portuguese (Brazil))
Currently translated at 99.2% (138 of 139 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-03-14 17:28:10 +01:00
Harvey Tindall
5892899114 thread compile_mjml 2021-03-13 17:05:59 +00:00
Peter Wickenberg
4404c84e7f Translated using Weblate (Swedish)
Currently translated at 100.0% (43 of 43 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/sv/
2021-03-13 17:48:06 +01:00
Peter Wickenberg
a86be55b5c translation from Weblate (Swedish)
Currently translated at 100.0% (130 of 130 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/sv/
2021-03-13 17:48:06 +01:00
Peter Wickenberg
5eea72a579 translation from Weblate (Swedish)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-03-13 17:48:06 +01:00
Harvey Tindall
03247ddef8 Add accounts search 2021-03-13 14:26:29 +00:00
Harvey Tindall
e6e5b0f3cf respect browser/os dark mode preference 2021-03-10 19:42:40 +00:00
Harvey Tindall
9b977bafbf add other funding method 2021-03-09 18:45:28 +00:00
Harvey Tindall
77f755e43c fix dropdown colors in dark mode on chrome 2021-03-09 18:15:14 +00:00
Harvey Tindall
30bef15855 Trim commit before comparing in IsNew()
Fixes the current version appearing as an update. Also fixed error
handling when no update is available, which obviously hadn't previously
been experienced.
2021-03-09 15:52:15 +00:00
Harvey Tindall
7bd8fadf76 IsNew() compares commit, not version 2021-03-07 17:24:45 +00:00
Harvey Tindall
21490faa9e fix IsNew() func, include LICENSE in goreleaser 2021-03-07 16:45:35 +00:00
Harvey Tindall
f685582e1a run upload.py in git directory 2021-03-07 16:27:15 +00:00
Harvey Tindall
f792166523 use locally stored buildrone key bcs ssh is broken 2021-03-07 16:15:31 +00:00
Harvey Tindall
7c0754a70c fix buildrone env 2021-03-07 16:07:55 +00:00
Harvey Tindall
2f33580f32 remove testing goreleaser script, oops 2021-03-07 15:55:28 +00:00
Harvey Tindall
eb8f2777ae fix naming conflict with goreleaser, add buildrone key to other steps 2021-03-07 15:54:32 +00:00
Harvey Tindall
92332206f0 add basic update functionality
If enabled, jfa-go pings buildrone (hosted at builds.hrfee.pw) every 30
min for new updates. If there is one, it gets information (and if
applicable, a binary) from the appropriate source (buildrone, github, or
dockerhub) and displays it on the admin page. You can switch update
channels between stable and unstable. For binary releases, updates are
downloaded automatically and installed when the user presses update.

Since this obviously introduces some "phone-home" functionality into
jfa-go, I just want to say IPs are not and will not be logged by
buildrone, although I may later introduce functionality to give a rough
idea of the number of users (again, no IPs stored). The whole thing can
also be turned off in settings.
2021-03-07 15:23:44 +00:00
ClankJake
9787fce275 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (43 of 43 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-03-02 16:34:29 +01:00
ClankJake
1c67b06c27 translation from Weblate (Portuguese (Brazil))
Currently translated at 98.4% (128 of 130 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-03-02 16:34:29 +01:00
ClankJake
88eab75e30 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/pt_BR/
2021-03-02 16:34:29 +01:00
ClankJake
6c5f776a7a translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-03-02 16:34:29 +01:00
Richard de Boer
ca0c56e748 Translated using Weblate (Dutch)
Currently translated at 100.0% (43 of 43 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-03-02 16:34:28 +01:00
Richard de Boer
e29e0ddb5b translation from Weblate (Dutch)
Currently translated at 100.0% (28 of 28 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/nl/
2021-03-02 16:34:28 +01:00
Richard de Boer
7ce75c271c translation from Weblate (Dutch)
Currently translated at 100.0% (130 of 130 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-03-02 16:34:28 +01:00
Harvey Tindall
884493e7aa add download links at top, mention jfa-go-bin 2021-03-02 01:30:08 +00:00
Harvey Tindall
bd05a4b35a include LICENSE in build, display in about tab
Also fixes last commit, user cache wasn't refreshed in ApplySettings, is
now.
2021-03-01 00:32:09 +00:00
Harvey Tindall
fa7da1b23f Don't use cache to ApplySettings and CreateProfile
also use a wrapper function to set default settings in config.go so it's
less ugly.
2021-02-28 18:26:22 +00:00
Harvey Tindall
1ec5d2ca3f add disabled badge, extend expiry button to accounts 2021-02-28 17:52:24 +00:00
Harvey Tindall
1e9d184508 implement user expiry functionality
All works now, but i'll add a field on the accounts tab for users with
an expiry, as well as a 'disabled' badge.
2021-02-28 15:41:06 +00:00
Harvey Tindall
2934832a98 implement frontend for user expiry/duration
this will add an optional validity period to users, where their account
will be disabled (or deleted) a specified amount of time after they
created it.
2021-02-28 00:44:28 +00:00
Harvey Tindall
3635b6a367 lowercase lang names 2021-02-24 21:49:29 +00:00
Peter Wickenberg
2b97850eb2 Translated using Weblate (Swedish)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/sv/
2021-02-24 22:48:17 +01:00
Peter Wickenberg
c1d1b0e560 Translated using Weblate (Swedish)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/sv/
2021-02-24 22:48:17 +01:00
Peter Wickenberg
e1d9a00d67 Translated using Weblate (Swedish)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/sv/
2021-02-24 22:48:17 +01:00
Peter Wickenberg
35aa37e10e translation from Weblate (Swedish)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/sv/
2021-02-24 22:48:17 +01:00
Peter Wickenberg
e38c470fb9 translation from Weblate (Swedish)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-02-24 22:48:17 +01:00
Peter Wickenberg
edd4584136 Translated using Weblate (Swedish)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/sv/
2021-02-24 15:59:00 +01:00
Peter Wickenberg
d7a84c1982 Translated using Weblate (Swedish)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/sv/
2021-02-24 15:59:00 +01:00
Peter Wickenberg
fe86b8a7d0 Translated using Weblate (Swedish)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/sv/
2021-02-24 15:59:00 +01:00
Peter Wickenberg
01f290b459 translation from Weblate (Swedish)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/sv/
2021-02-24 15:59:00 +01:00
Peter Wickenberg
9a398e9291 translation from Weblate (Swedish)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-02-24 15:59:00 +01:00
Peter Wickenberg
1fbd11dbe8 Translated using Weblate (Swedish)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/sv/
2021-02-24 15:41:33 +01:00
Peter Wickenberg
68b26f8301 Translated using Weblate (Swedish)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/common-strings/sv/
2021-02-24 15:41:32 +01:00
Peter Wickenberg
6877f3975e translation from Weblate (Swedish)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/sv/
2021-02-24 15:41:32 +01:00
Peter Wickenberg
6a11ed5622 translation from Weblate (Swedish)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/sv/
2021-02-24 15:41:31 +01:00
Peter Wickenberg
53bec00a7e Translated using Weblate (Swedish)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/sv/
2021-02-24 15:41:31 +01:00
Peter Wickenberg
c616ab324d Added translation using Weblate (Swedish) 2021-02-23 23:33:04 +01:00
Peter Wickenberg
7e21eb87db Added translation using Weblate (Swedish) 2021-02-23 23:32:52 +01:00
Peter Wickenberg
98cd33da05 Added translation using Weblate (Swedish) 2021-02-23 23:32:40 +01:00
Peter Wickenberg
d520694e12 add translation from Weblate (Swedish) 2021-02-23 23:32:30 +01:00
Peter Wickenberg
3c4800efa8 add translation from Weblate (Swedish) 2021-02-23 23:25:42 +01:00
mezzovide
bd227842d2 translation from Weblate (Indonesian)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/id/
2021-02-22 18:53:04 +01:00
mezzovide
f47bf762ac Translated using Weblate (Indonesian)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/id/
2021-02-22 18:53:04 +01:00
ClankJake
1342208980 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-02-22 18:53:03 +01:00
virusperfect
c8a9b15b4e Translated using Weblate (German)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/de/
2021-02-22 18:53:03 +01:00
virusperfect
b0bd6973d1 translation from Weblate (German)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/de/
2021-02-22 18:53:02 +01:00
Killianbe
d10eb6d6bf Translated using Weblate (French)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/fr/
2021-02-22 18:53:02 +01:00
Richard de Boer
0c5a332fa2 translation from Weblate (Dutch)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-02-22 18:53:02 +01:00
Killianbe
5a07e103c0 translation from Weblate (French)
Currently translated at 100.0% (118 of 118 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-02-22 18:53:01 +01:00
Harvey Tindall
40fc5e9604 Fix email editor when plaintext setting enabled 2021-02-22 16:40:37 +00:00
Harvey Tindall
9799665951 fix tag versioning and dockerfile 2021-02-22 01:26:07 +00:00
Harvey Tindall
b3fa667db1 version with ldflags instead of script 2021-02-22 01:23:42 +00:00
Harvey Tindall
027cf19d0f delete missing route bind 2021-02-22 01:05:18 +00:00
Harvey Tindall
38119551d7 merge translation 2021-02-22 00:45:07 +00:00
Harvey Tindall
52d9cda61a Move email rendering to browser
the email preview no longer has a delay after each change. This also
avoids a race condition in which the email currently being edited could
be actually sent.
2021-02-22 00:43:36 +00:00
ClankJake
f40fb9d3f7 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/pt_BR/
2021-02-21 16:55:47 +01:00
ClankJake
9536ceaaa4 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (110 of 110 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/pt_BR/
2021-02-21 16:55:47 +01:00
ClankJake
72beee1322 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/pt_BR/
2021-02-21 16:55:47 +01:00
Richard de Boer
0ec822988d Translated using Weblate (Dutch)
Currently translated at 100.0% (39 of 39 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-02-21 16:55:47 +01:00
Harvey Tindall
d1b1b90de3 Add email list accessible by edit button in settings 2021-02-21 15:51:42 +00:00
Harvey Tindall
058cac2e7b implement email editor w/ live(?) preview
not accessible in the ui currently, but the object is available as
window.ee for testing.
2021-02-20 22:49:59 +00:00
Harvey Tindall
6ffdd4dad7 fix mistype in german email 2021-02-20 01:31:34 +00:00
Harvey Tindall
98d59ba4e0 don't strip text on images 2021-02-20 01:20:43 +00:00
Harvey Tindall
938523c18b fix urls in custom email/announcements
Uses a nasty algorithm found in stripmd.go to change all occurrences
of '[linktext](link)' to just 'link' before passing to a decent markdown
stripper.
2021-02-20 01:03:11 +00:00
Harvey Tindall
cc4e12c405 finish backend of custom emails
biggest bodge i've ever done but it works i guess.
2021-02-20 00:22:40 +00:00
Harvey Tindall
eb406ef951 Implement email template generation
Variables are surrounded by {}, and initial (default) templates are
generated on demand from the plaintext version of emails. The custom
emails are intended to only be used if the user actually changes them,
as they lose the features of the default ones, such as tables.
2021-02-19 21:38:20 +00:00
Harvey Tindall
5c87d109a3 use descriptive variable names in email translations
in preparation for an email editor.
2021-02-19 17:50:50 +00:00
Harvey Tindall
3e020da66a merge translation 2021-02-19 16:12:27 +00:00
Harvey Tindall
78157f763f use different color library, wrap logger functions with it 2021-02-19 16:12:14 +00:00
Richard de Boer
bcc0eeeb2f Translated using Weblate (Dutch)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/setup/nl/
2021-02-19 15:58:27 +01:00
Richard de Boer
76b859f5bf Translated using Weblate (Dutch)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/emails/nl/
2021-02-19 15:58:27 +01:00
Richard de Boer
676cf619d5 translation from Weblate (Dutch)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/form/nl/
2021-02-19 15:58:27 +01:00
Richard de Boer
ce45bf2136 translation from Weblate (Dutch)
Currently translated at 100.0% (110 of 110 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/nl/
2021-02-19 15:58:26 +01:00
Harvey Tindall
b25f786018 use bulk email sending on account deletion 2021-02-19 14:51:36 +00:00
Harvey Tindall
ca00796077 Merge pull request #61 from rigrig/main
use `apt-get` instead of `apt` in Dockerfile
2021-02-19 14:47:10 +00:00
Richard de Boer
a1bbf13d6a use apt-get instead of apt
Because `apt` is meant for humans, and complains when called in scripts.
(manpage: "While it tries not to break backward compatibility this is not guaranteed")
2021-02-19 15:26:11 +01:00
Harvey Tindall
76fa171575 cleanup logs and use structs in jf/emby api
Also means times are directly parsed when pulling data from jf/emby,
which was *painful* to get working (something broke the whole program and it
took me an hour to figure out it was this lol). Time parsing should be a
lot stabler too.
2021-02-19 00:47:01 +00:00
Killianbe
ce30537ebd translation from Weblate (French)
Currently translated at 100.0% (110 of 110 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.dev/projects/jfa-go/admin/fr/
2021-02-18 19:34:07 +01:00
Harvey Tindall
93b5b483cc add plaintext email option, use text/template
text/template is used on plaintext emails to avoid escaping of certain
characters.
2021-02-18 18:26:23 +00:00
Harvey Tindall
27ef931670 add possible dark mode fix for Outlook 2021-02-18 17:47:15 +00:00
Harvey Tindall
fb727e75ec substitute jellyfin strings on emails, hopefully fix dark mode 2021-02-18 16:09:58 +00:00
Harvey Tindall
fa433c88a8 add announcement emails
After selecting users in the accounts tab, you can press 'Announce',
then write a subject and message (with markdown), and an email will be
sent to each selected user.
2021-02-18 14:58:53 +00:00
Harvey Tindall
adbb5b9d38 Fix filepath separator and external files on windows
For some reason, '/' is used instead of '\' on windows when loading
lang. FSJoin will now use whatever already exists in the path.
app.GetPath now creates a DirFS from the containing directory instead of
app.systemFS, which fixes loading on windows.
2021-02-18 12:58:30 +00:00
Harvey Tindall
cdc837e781 trim '/' from path when using systemFS
should fix #58.
2021-02-17 22:02:26 +00:00
Harvey Tindall
a92baa5d18 update urls 2021-02-17 20:52:49 +00:00
Harvey Tindall
e913f25a47 update buildrone url 2021-02-17 20:31:52 +00:00
Harvey Tindall
9eb803388e add it-it email back 2021-02-17 16:54:09 +00:00
Harvey Tindall
eb81515f46 fix ordering of steps in dockerfile 2021-02-17 16:53:15 +00:00
Harvey Tindall
52461c0356 add it-it email back 2021-02-17 16:36:39 +00:00
Harvey Tindall
6b3800abf6 remove branch trigger 2021-02-17 16:34:04 +00:00
Harvey Tindall
09fc81d7f4 fix tag recognition by drone 2021-02-17 16:26:14 +00:00
Harvey Tindall
82034a2586 use python3 in makefile for embed 2021-02-17 16:11:01 +00:00
Harvey Tindall
5e001bed60 temporarily modify lang for release
Fixed name on Indonesian and removed Italian emails.
2021-02-17 16:04:25 +00:00
Harvey Tindall
5d7972db56 rename embed/noembed to internal-files/external-files 2021-02-17 14:41:44 +00:00
Harvey Tindall
403ad58274 move all scripts to scripts/ 2021-02-17 14:32:03 +00:00
Harvey Tindall
a1a233e74f fix sed path in Dockerfile 2021-02-17 11:33:41 +00:00
Harvey Tindall
8dd72c95ab switch image for drone builds 2021-02-17 11:19:51 +00:00
Harvey Tindall
f794322392 Merge branch 'go1.16'
merge go1.16 changes

This includes embedded files for releases (no extra 'data' directory!)
and support for a custom language file directory, allowing one to
customize the text accross the app.
2021-02-17 11:15:21 +00:00
Harvey Tindall
afd52d1d37 Use go cross-compilation in docker build
significantly faster builds now. Every pre-compilation step (typescript,
css, etc.) happens natively, then GOARCH=xxx make compile is also run
natively for each architecture. The output it then copied into each
container.
2021-02-16 16:08:53 +00:00
Harvey Tindall
ba7370171a lowercase lang, go mod tidy 2021-02-16 14:31:31 +00:00
frankwalter1301
deb364a8bd Translated using Weblate (Italian)
Currently translated at 71.8% (23 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/it/
2021-02-16 15:16:58 +01:00
frankwalter1301
a3cf498e15 translation from Weblate (Italian)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/it/
2021-02-16 15:16:58 +01:00
mezzovide
3b356d2d8c Translated using Weblate (Indonesian)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/id/
2021-02-16 15:16:58 +01:00
Etienne dP
20e17b576a translation from Weblate (French)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/fr/
2021-02-16 15:16:58 +01:00
frankwalter1301
8e680ff576 Added translation using Weblate (Italian) 2021-02-16 15:16:58 +01:00
frankwalter1301
29d26aeb15 add translation from Weblate (Italian) 2021-02-16 15:16:58 +01:00
Harvey Tindall
33b7876826 build from tag on stable 2021-02-13 22:03:35 +00:00
Harvey Tindall
0fc4b5eb22 switch to buildx with qemu for builds 2021-02-13 21:59:33 +00:00
Harvey Tindall
e672f9f14c lowercase lang names 2021-02-13 20:51:35 +00:00
Harvey Tindall
4d2e509950 merge language changes 2021-02-13 20:50:27 +00:00
Harvey Tindall
a80e5c2aa9 purge manifest and recreate per build 2021-02-13 20:49:56 +00:00
Marketos Damigos
cd375208ba Translated using Weblate (Greek)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/el/
2021-02-13 17:57:39 +01:00
Marketos Damigos
316f482bf5 Translated using Weblate (Greek)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/el/
2021-02-13 17:57:39 +01:00
Marketos Damigos
0f78390282 translation from Weblate (Greek)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/el/
2021-02-13 17:57:39 +01:00
Marketos Damigos
0b909fc02d Translated using Weblate (Greek)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/common-strings/el/
2021-02-13 17:57:39 +01:00
mezzovide
d4c6561abd Translated using Weblate (Indonesian)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/id/
2021-02-13 17:57:39 +01:00
Marketos Damigos
1f5e6537a5 translation from Weblate (Greek)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/el/
2021-02-13 17:57:39 +01:00
mezzovide
97f2ae34ca Translated using Weblate (Indonesian)
Currently translated at 1.0% (1 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/id/
2021-02-13 17:57:39 +01:00
mezzovide
d193afbeca translation from Weblate (Indonesian)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/id/
2021-02-13 17:57:39 +01:00
mezzovide
abea430b6b Translated using Weblate (Indonesian)
Currently translated at 100.0% (9 of 9 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/common-strings/id/
2021-02-13 17:57:39 +01:00
mezzovide
00cec2b157 translation from Weblate (Indonesian)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/id/
2021-02-13 17:57:39 +01:00
Marketos Damigos
41ff0be839 Translated using Weblate (Greek)
Currently translated at 1.0% (1 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/el/
2021-02-13 17:57:39 +01:00
mezzovide
25330533bd add translation from Weblate (Indonesian) 2021-02-13 17:57:39 +01:00
mezzovide
4afd1bd4b5 Added translation using Weblate (Indonesian) 2021-02-13 17:57:39 +01:00
mezzovide
cf185efdfc add translation from Weblate (Indonesian) 2021-02-13 17:57:39 +01:00
mezzovide
4ec9756f58 Added translation using Weblate (Indonesian) 2021-02-13 17:57:39 +01:00
mezzovide
cda7db5718 Added translation using Weblate (Indonesian) 2021-02-13 17:57:39 +01:00
Marketos Damigos
3b37fb5692 Added translation using Weblate (Greek) 2021-02-13 17:57:39 +01:00
Marketos Damigos
40c83803de Added translation using Weblate (Greek) 2021-02-13 17:57:39 +01:00
Marketos Damigos
a9811c164e Added translation using Weblate (Greek) 2021-02-13 17:57:39 +01:00
Marketos Damigos
8f000876b3 add translation from Weblate (Greek) 2021-02-13 17:57:39 +01:00
Marketos Damigos
0af393236f add translation from Weblate (Greek) 2021-02-13 17:57:39 +01:00
hrfee
3153c65f5a Translated using Weblate (English)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/en/
2021-02-13 17:57:39 +01:00
virusperfect
6ce825bd41 translation from Weblate (German)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/de/
2021-02-13 17:57:38 +01:00
Harvey Tindall
060f0efc16 pull containers before manifest, run on different host 2021-02-13 14:05:04 +00:00
Harvey Tindall
c3fb00a307 wrong go version container 2021-02-12 15:37:19 +00:00
Harvey Tindall
988829a6db dont build docker on go1.16 branch 2021-02-12 15:22:21 +00:00
Harvey Tindall
76935a300a dont build docker on go1.16 branch 2021-02-12 15:21:58 +00:00
Harvey Tindall
a6a7710a79 use filepath.Join wrapper for different embed and os path styles
If using internal, "/" is used as a separator always, and with external,
filepath.Join is used.
2021-02-12 14:59:35 +00:00
Harvey Tindall
873afb47cd strip debug symbols in makefile 2021-02-12 14:59:35 +00:00
Harvey Tindall
ea99966057 refactor, move route loading to router.go 2021-02-12 14:59:16 +00:00
Harvey Tindall
aaed272bf2 use embed.fs wrapper on data 2021-02-12 14:35:16 +00:00
Harvey Tindall
e6775cd2d1 use embed.fs wrapper for langFS so lang/ is not needed in paths
[files]lang_files is now the path to the lang directory, not path to a
directory containing it.
2021-02-12 14:35:16 +00:00
Harvey Tindall
98a9e20cc0 Fix docker build, add GOBINARY flag for make
GOBINARY defaults to "go", but if you want to build on a normal system,
you'll likely set it to go1.16rc1 with "make all GOBINARY=go1.16rc1".
2021-02-12 14:35:13 +00:00
Harvey Tindall
ee37588959 drone image 2021-02-12 14:32:57 +00:00
Harvey Tindall
cb12c6f441 update goreleaser 2021-02-12 14:32:57 +00:00
Harvey Tindall
72cf3e2240 add external/internal data options
"make all" will build with internal data, whereas "make debug"/"make
all-external" will make an external "data/" directory.
2021-02-12 14:32:48 +00:00
Harvey Tindall
815bdc35ac fully self-contained
paths are pretty janky, but it works. Also, [files]/lang_files now must
be the path to a directory CONTAINING a "lang/" directory. I'll work
around this at a later date.
2021-02-12 14:28:09 +00:00
Harvey Tindall
0330540f87 Use fs for language, add lang_files option
The local app translations are loaded, and then if [files]/lang_files
is provided (a directory containing custom translations), any found
inside it are loaded over top. This makes customizing much easier.
2021-02-12 14:28:09 +00:00
Harvey Tindall
fefe2d82a4 rebase 12/02, use go1.16rc1 in make, remove ioutil, start switching to io/fs for file i/o
ioutil's contents are now in io and os.
Eventually jfa-go's files will be embedded in the binary with go1.16's
new embed feature. Using io/fs will provide abstraction for accessing
these files, and allow for both embedded and non-embedded versions.
Also, internal paths to things like email templates, etc. will be
prefixed with "jfa-go:" to indicate to use the app's own Filesystem
instead of reading the file normally. This also allows for custom files
to continue to be used as they are currently.
2021-02-12 14:27:01 +00:00
Harvey Tindall
1af8d1f77d fix url in account creation success page 2021-02-12 13:38:34 +00:00
Harvey Tindall
4c653fea36 fix url base on invite and broken getLanguages 2021-02-12 12:52:08 +00:00
Harvey Tindall
2ee0ed55f6 forgot key agh 2021-02-12 00:35:20 +00:00
Harvey Tindall
94981f4891 dont use drone manifest plugin 2021-02-11 23:52:05 +00:00
Harvey Tindall
f72def0399 serve on / and URL base for easy proxying 2021-02-11 23:06:51 +00:00
Harvey Tindall
81fb0fc69f fix triggers aarch64 = arm64 2021-02-11 22:25:00 +00:00
Harvey Tindall
c3af0f4380 remove tag event from unstable build 2021-02-11 21:48:57 +00:00
Harvey Tindall
3a9e4950d4 run docker amd64 builds on drone, attempt multiarch 2021-02-11 21:18:32 +00:00
Harvey Tindall
06dada297b up command_timeout for slow rpi builds 2021-02-11 18:47:24 +00:00
Harvey Tindall
2b55a1873c fix css, oops 2021-02-11 18:28:25 +00:00
Harvey Tindall
c2e68bdc77 add GOESBUILD option for platform without esbuild on npm 2021-02-11 18:21:21 +00:00
Harvey Tindall
e1c3b312ff split armhf and arm64, add stable build 2021-02-11 16:24:32 +00:00
Harvey Tindall
e235ed9fda fix key again 2021-02-11 16:14:39 +00:00
Harvey Tindall
5cda12dd3b separate into pipelines, add armhf 2021-02-11 16:11:07 +00:00
Harvey Tindall
a9d48083fd fix keyfile 2021-02-11 15:48:13 +00:00
Harvey Tindall
e28c50401e use key path 2021-02-11 15:37:02 +00:00
Harvey Tindall
4a3b015a40 start adding automated arm builds 2021-02-11 15:29:33 +00:00
Harvey Tindall
1a6727312c dont override header on email confirmation fail 2021-02-11 14:04:15 +00:00
Harvey Tindall
91d3d2596e fix broken invite links 2021-02-11 13:49:06 +00:00
Harvey Tindall
192c9a4764 account for lack of trailing slash in url 2021-02-09 20:45:35 +00:00
Harvey Tindall
173c38563e remove embed, oops 2021-02-08 15:43:20 +00:00
Harvey Tindall
d061721f56 explicitly set js mimetype 2021-02-08 15:25:02 +00:00
Harvey Tindall
218882b7c6 remove debug console.log 2021-02-08 11:50:58 +00:00
Harvey Tindall
fed3ee4c4f Create FUNDING.yml 2021-02-08 01:01:48 +00:00
Harvey Tindall
8eed4b0127 merge language again 2021-02-05 18:25:56 +00:00
Harvey Tindall
c09ffb49e7 switch emails to normal text when not editing
fixes padding on small screens.
2021-02-05 18:24:27 +00:00
ClankJake
f331f4eb92 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/pt_BR/
2021-02-05 15:55:38 +01:00
Richard de Boer
629b669c64 translation from Weblate (Dutch)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/nl/
2021-02-05 15:55:38 +01:00
Cornichon420
2dab900748 translation from Weblate (French)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/fr/
2021-02-05 15:55:38 +01:00
hrfee
f864097f2e translation from Weblate (English)
Currently translated at 100.0% (103 of 103 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/en/
2021-02-05 15:55:38 +01:00
Harvey Tindall
2c8be42bbc fix invite links with URL base 2021-02-05 13:33:34 +00:00
Harvey Tindall
6691ae27f4 fix navigation with URL base set 2021-02-05 13:31:56 +00:00
Harvey Tindall
23fecb16b2 merge language changes 2021-02-05 13:11:00 +00:00
Harvey Tindall
b037b08152 respect URL Base in http preloads and inline html links 2021-02-05 13:10:47 +00:00
ClankJake
46fe3a7f5d Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/pt_BR/
2021-02-03 10:04:41 +01:00
virusperfect
61bd62403f Translated using Weblate (German)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/de/
2021-02-03 10:04:41 +01:00
ClankJake
5893d4b855 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/pt_BR/
2021-02-03 10:04:41 +01:00
virusperfect
8016e6f211 Translated using Weblate (German)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/de/
2021-02-03 10:04:41 +01:00
virusperfect
a5560b04bd translation from Weblate (German)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/de/
2021-02-03 10:04:41 +01:00
Richard de Boer
b9e171b1fd Translated using Weblate (Dutch)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/nl/
2021-02-03 10:04:41 +01:00
Cornichon420
a633425baa Translated using Weblate (French)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/fr/
2021-02-03 10:04:41 +01:00
Richard de Boer
e29e89c618 translation from Weblate (Dutch)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/nl/
2021-02-03 10:04:41 +01:00
Cornichon420
62c986161c translation from Weblate (French)
Currently translated at 100.0% (27 of 27 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/fr/
2021-02-03 10:04:41 +01:00
ClankJake
6279c73402 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/pt_BR/
2021-02-03 10:04:41 +01:00
ClankJake
22e103837f translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/pt_BR/
2021-02-03 10:04:41 +01:00
ClankJake
feba6e7bae Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/pt_BR/
2021-02-03 10:04:41 +01:00
Harvey Tindall
95a6b48c3e add go1.16 branch do drone builds
This branch has fully self-contained binaries, so I thought it'd be a
good idea to build it alongside.
2021-02-01 18:43:30 +00:00
Harvey Tindall
90c6cee780 add restart button 2021-01-31 19:01:20 +00:00
Harvey Tindall
456ef556b1 add inter-section dependency for settings
Currently used to hide all email sections when [email]/method is blank
(disabled).
2021-01-31 18:50:04 +00:00
Harvey Tindall
ce98b2eb5a add backwards navigation 2021-01-31 17:32:03 +00:00
Harvey Tindall
ee026714d4 Add optional email confirmation
If enabled, a confirmation email will be sent before the user can create
their account.
2021-01-30 19:19:12 +00:00
Harvey Tindall
736c39840f fix default jellyfin path for setup in dockerfile 2021-01-29 17:13:15 +00:00
Harvey Tindall
e755bc6b61 fix language names 2021-01-29 13:49:58 +00:00
Richard de Boer
7ec9f2435c translation from Weblate (Dutch)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/nl/
2021-01-29 14:42:20 +01:00
Cornichon420
443d6fee52 Translated using Weblate (French)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/fr/
2021-01-29 14:42:20 +01:00
Cornichon420
b023616033 translation from Weblate (French)
Currently translated at 100.0% (25 of 25 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/fr/
2021-01-29 14:42:20 +01:00
Cornichon420
f182b88c58 Translated using Weblate (French)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/fr/
2021-01-29 14:42:20 +01:00
Richard de Boer
93daadae4b translation from Weblate (Dutch)
Currently translated at 100.0% (25 of 25 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/nl/
2021-01-29 14:42:20 +01:00
virusperfect
fd1ec5d3fb Translated using Weblate (German)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/de/
2021-01-29 14:42:20 +01:00
virusperfect
b9a8a27807 translation from Weblate (German)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/de/
2021-01-29 14:42:20 +01:00
Cornichon420
27a36898a3 translation from Weblate (French)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/fr/
2021-01-29 14:42:20 +01:00
Richard de Boer
d8948c037b Translated using Weblate (Dutch)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/nl/
2021-01-29 14:42:20 +01:00
virusperfect
83f2749eab Translated using Weblate (German)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/de/
2021-01-29 14:42:20 +01:00
virusperfect
290435b5ba translation from Weblate (German)
Currently translated at 100.0% (25 of 25 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/de/
2021-01-29 14:42:20 +01:00
Cornichon420
d09125c63c Translated using Weblate (French)
Currently translated at 100.0% (8 of 8 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/common-strings/fr/
2021-01-29 14:42:20 +01:00
Cornichon420
f0aa64373b Added translation using Weblate (French) 2021-01-29 14:42:20 +01:00
DesertCookie
2272883d5a translation from Weblate (German)
Currently translated at 100.0% (25 of 25 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/de/
2021-01-29 14:42:20 +01:00
Richard de Boer
2d0f6d89aa Translated using Weblate (Dutch)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/nl/
2021-01-29 14:42:20 +01:00
hrfee
4cd1571c05 Translated using Weblate (Dutch)
Currently translated at 100.0% (95 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/nl/
2021-01-29 14:42:20 +01:00
ClankJake
55be62bc3e Translated using Weblate (Portuguese (Brazil))
Currently translated at 98.9% (94 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/pt_BR/
2021-01-29 14:42:20 +01:00
ClankJake
da82f1c146 translation from Weblate (Portuguese (Brazil))
Currently translated at 100.0% (25 of 25 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/pt_BR/
2021-01-29 14:42:20 +01:00
DesertCookie
67f53d4112 Translated using Weblate (German)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/de/
2021-01-29 14:42:20 +01:00
ClankJake
88356281fb translation from Weblate (Portuguese (Brazil))
Currently translated at 97.0% (98 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/pt_BR/
2021-01-29 14:42:20 +01:00
ClankJake
05198ea764 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/pt_BR/
2021-01-29 14:42:20 +01:00
ClankJake
fe33d97d87 Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (8 of 8 strings)

Translation: jfa-go/Common Strings
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/common-strings/pt_BR/
2021-01-29 14:42:20 +01:00
DesertCookie
2c60dee48a translation from Weblate (German)
Currently translated at 100.0% (101 of 101 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/de/
2021-01-29 14:42:20 +01:00
Richard de Boer
8af9f9944a Translated using Weblate (Dutch)
Currently translated at 72.6% (69 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/nl/
2021-01-29 14:42:20 +01:00
DesertCookie
4e968d2338 Translated using Weblate (German)
Currently translated at 97.8% (93 of 95 strings)

Translation: jfa-go/Setup
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/setup/de/
2021-01-29 14:42:20 +01:00
Richard de Boer
7ba88977a4 Added translation using Weblate (Dutch) 2021-01-29 14:42:20 +01:00
hrfee
0c5f6a68f9 Added translation using Weblate (Portuguese (Brazil)) 2021-01-29 14:42:20 +01:00
ClankJake
bbd539278c add translation from Weblate (Portuguese (Brazil)) 2021-01-29 14:42:20 +01:00
DesertCookie
be9d9ac6ff Added translation using Weblate (German) 2021-01-29 14:42:20 +01:00
ClankJake
e8b37a5df8 Added translation using Weblate (Portuguese (Brazil)) 2021-01-29 14:42:20 +01:00
ClankJake
d10d347e2b Added translation using Weblate (Portuguese (Brazil)) 2021-01-29 14:42:20 +01:00
ClankJake
68689d74a0 add translation from Weblate (Portuguese (Brazil)) 2021-01-29 14:42:20 +01:00
Harvey Tindall
4fc9bdb35b element already existed, oops 2021-01-29 01:32:44 +00:00
Harvey Tindall
c0a05be44e add strftime notice on setup
string was already in translations, just forgot to include it.
2021-01-29 01:29:54 +00:00
Harvey Tindall
482c9d5719 update license date 2021-01-29 01:20:05 +00:00
virusperfect
f063298bf7 Translated using Weblate (German)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/de/
2021-01-27 23:11:35 +01:00
virusperfect
bb1e454850 translation from Weblate (German)
Currently translated at 100.0% (108 of 108 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/de/
2021-01-27 23:11:35 +01:00
Cornichon420
11770d90f1 translation from Weblate (French)
Currently translated at 100.0% (108 of 108 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/fr/
2021-01-27 23:11:35 +01:00
hrfee
8a415140b6 Translated using Weblate (English)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/en/
2021-01-27 23:11:35 +01:00
Harvey Tindall
3dd83bffbf Merge branch 'new-setup'
Merge new setup wizard

This is much more up-to-date than the previous setup page, with a new
design and previously missing/new settings. Currently only available in
english (hopefully that changes soon).

also fixes conflict in _post.
2021-01-27 22:06:44 +00:00
Harvey Tindall
79987ffa22 add refresh button that uses url base if given 2021-01-27 21:51:01 +00:00
Harvey Tindall
764639bbba add header template 2021-01-27 21:38:35 +00:00
Harvey Tindall
eb67116ee6 replace og setup 2021-01-27 21:37:43 +00:00
Harvey Tindall
7baea9101e Add general settings, ombi
host, port, theme, tls are included in general. Page theme changes with
setting. Fixed checkbox support messages. Split some cards into columns.
2021-01-27 21:35:41 +00:00
Harvey Tindall
167fae9892 add jellyfin connection test, submission
fully functional now, but still need to add some sections (ombi mainly).
2021-01-27 12:55:39 +00:00
Harvey Tindall
c7f5aa2e2b split into pages, hide email pages when disabled, add history navigation 2021-01-27 00:51:19 +00:00
Harvey Tindall
8c871bc5fa Add ts to link setting dependance
Also make store each setting as classes in a settings object, to make it
easier to serialize on submitting. Also, added
"substitute_jellyfin_strings", "no_username" and welcome_email.
2021-01-26 22:57:29 +00:00
Harvey Tindall
1f6bbc75ff remove junk files 2021-01-26 00:39:35 +00:00
Harvey Tindall
23ae18d732 compile setup.ts in Makefile/Goreleaser
surprised there hasn't been issues for this, the setup page would've
been broken for a while.
2021-01-26 00:37:32 +00:00
Harvey Tindall
bf1e6230dc split some strings into common file; use lang file to setup page 2021-01-25 21:26:54 +00:00
Harvey Tindall
8af1c13d7e Display error messages on form
two new strings need translating in lang/form.
2021-01-25 18:01:18 +00:00
Harvey Tindall
061945218a fix extra whitespace after pin code
for #39
2021-01-25 17:18:35 +00:00
Harvey Tindall
687edf2b0b Initial setup page content
Rewritten with a17t. Content right now is just a copy of the original
setup.html, but settings for new features will be added later.Currently
all cards are shown, only the current one will show in future.
2021-01-24 23:05:04 +00:00
Cornichon420
1bf1e994fe Translated using Weblate (French)
Currently translated at 100.0% (32 of 32 strings)

Translation: jfa-go/Emails
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/emails/fr/
2021-01-24 18:27:28 +01:00
Cornichon420
7f91a27e4f translation from Weblate (French)
Currently translated at 100.0% (23 of 23 strings)

Translation: jfa-go/Account Creation Form
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/form/fr/
2021-01-24 18:27:28 +01:00
Cornichon420
f66510c74b translation from Weblate (French)
Currently translated at 100.0% (108 of 108 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/fr/
2021-01-24 18:27:28 +01:00
Weblate
e5de8b20ff merge branch 'origin/main' into Weblate. 2021-01-24 16:57:46 +01:00
Harvey Tindall
dd96d71280 Add optional label for invites
Requested in #38.
2021-01-24 15:55:45 +00:00
Richard de Boer
a687b2c438 translation from Weblate (Dutch)
Currently translated at 100.0% (105 of 105 strings)

Translation: jfa-go/Admin Page
Translate-URL: https://weblate.hrfee.pw/projects/jfa-go/admin/nl/
2021-01-24 16:22:39 +01:00
Harvey Tindall
ea262ca60b add optional welcome email for new users
When enabled, an email with the server URL and username will be sent to
created users. Requested in #38.
2021-01-24 15:19:58 +00:00
Harvey Tindall
406fef6595 bundle typescript 2021-01-23 19:08:27 +00:00
Harvey Tindall
f7d8feac5d bundle css with esbuild 2021-01-23 18:53:14 +00:00
310 changed files with 51295 additions and 6921 deletions

View File

@@ -9,19 +9,69 @@ steps:
commands:
- git fetch --tags
- name: release
image: golang:latest
image: hrfee/jfa-go-build-docker:latest
volumes:
- name: ssh_key
path: /id_rsa
environment:
BUILDRONE_KEY:
from_secret: BUILDRONE_KEY
GITHUB_TOKEN:
from_secret: github_token
JFA_GO_BUILT_BY:
from_secret: BUILT_BY
commands:
- apt update -y
- apt install build-essential python3-pip curl software-properties-common sed upx -y
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
- apt install nodejs
- curl -sL https://git.io/goreleaser | bash
when:
event: tag
- curl -sL https://git.io/goreleaser > ../goreleaser
- chmod +x ../goreleaser
- ./scripts/version.sh ../goreleaser
- wget https://builds.hrfee.pw/upload.py -P ../
- pip3 install requests
- 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 "repo-process-deb trusty"'
bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "rm /repo/incoming/*.deb"'
- bash -c 'python3 ../upload.py https://builds.hrfee.pw hrfee jfa-go --tag internal=true'
volumes:
- name: ssh_key
host:
path: /root/.ssh/id_rsa_packaging
trigger:
event:
- tag
---
name: docker-buildx
kind: pipeline
type: docker
steps:
- name: build-deploy
image: appleboy/drone-ssh
volumes:
- name: ssh_key
path: /root/drone_rsa
settings:
host:
from_secret: ssh2_host
username:
from_secret: ssh2_username
port:
from_secret: ssh2_port
volumes:
- /root/.ssh/docker-build:/root/drone_rsa
key_path: /root/drone_rsa
command_timeout: 50m
script:
- /mnt/buildx/jfa-go/build.sh stable
- wget https://builds.hrfee.pw/upload.py -O /mnt/buildx/jfa-go/jfa-go/upload.py
- pip3 install requests
- bash -c 'cd /mnt/buildx/jfa-go/jfa-go && BUILDRONE_KEY=$(cat /mnt/buildx/jfa-go/key) python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-stable=true'
- rm -f /mnt/buildx/jfa-go/jfa-go/upload.py
trigger:
event:
- tag
volumes:
- name: ssh_key
host:
path: /root/.ssh/docker-build
---
name: jfa-go-git
kind: pipeline
@@ -29,22 +79,74 @@ type: docker
steps:
- name: build
image: golang:latest
image: hrfee/jfa-go-build-docker:latest
volumes:
- name: ssh_key
path: /id_rsa
- name: ssh_key2
path: /id_rsa2
commands:
- apt update -y
- apt install build-essential python3-pip curl software-properties-common sed upx -y
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
- apt install nodejs
- curl -sL https://git.io/goreleaser > goreleaser.sh
- chmod +x goreleaser.sh
- ./goreleaser.sh --snapshot --skip-publish --rm-dist
- curl -sL https://git.io/goreleaser > goreleaser
- chmod +x goreleaser
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --clean
- wget https://builds.hrfee.pw/upload.py
- pip3 install requests
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go ./dist/*.tar.gz'
- 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"'
- bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "repo-process-deb trusty"'
bash -c 'ssh -i /id_rsa root@161.97.102.153 -p 2022 "rm /repo/incoming/*.deb"'
- bash -c 'python3 upload.py https://builds.hrfee.pw hrfee jfa-go --upload ./dist/*.zip ./dist/*.rpm ./dist/*.apk --tag internal-git=true'
environment:
BUILDRONE_KEY:
from_secret: BUILDRONE_KEY
JFA_GO_BUILT_BY:
from_secret: BUILT_BY
volumes:
- name: ssh_key
host:
path: /root/.ssh/id_rsa_packaging
- name: ssh_key2
host:
path: /root/.ssh/docker-build
trigger:
branch:
- main
- go1.16
event:
exclude:
- pull_request
---
name: docker-buildx-unstable
kind: pipeline
type: docker
steps:
- name: build-deploy
image: appleboy/drone-ssh
volumes:
- name: ssh_key
path: /root/drone_rsa
settings:
host:
from_secret: ssh2_host
username:
from_secret: ssh2_username
port:
from_secret: ssh2_port
volumes:
- /root/.ssh/docker-build:/root/drone_rsa
key_path: /root/drone_rsa
command_timeout: 50m
script:
- /mnt/buildx/jfa-go/build.sh
- wget https://builds.hrfee.pw/upload.py -O /mnt/buildx/jfa-go/jfa-go/upload.py
- pip3 install requests
- bash -c 'cd /mnt/buildx/jfa-go/jfa-go && BUILDRONE_KEY=$(cat /mnt/buildx/jfa-go/key) python3 upload.py https://builds.hrfee.pw hrfee jfa-go --tag docker-unstable=true'
- rm -f /mnt/buildx/jfa-go/jfa-go/upload.py
trigger:
branch:
- main
@@ -52,6 +154,10 @@ trigger:
exclude:
- pull_request
volumes:
- name: ssh_key
host:
path: /root/.ssh/docker-build
---
name: jfa-go-pr
kind: pipeline
@@ -59,15 +165,11 @@ type: docker
steps:
- name: build
image: golang:latest
image: hrfee/jfa-go-build-docker:latest
commands:
- apt update -y
- apt install build-essential python3-pip curl software-properties-common sed upx -y
- (curl -sL https://deb.nodesource.com/setup_14.x | bash -)
- apt install nodejs
- curl -sL https://git.io/goreleaser > goreleaser.sh
- chmod +x goreleaser.sh
- ./goreleaser.sh --snapshot --skip-publish --rm-dist
- curl -sL https://git.io/goreleaser > goreleaser
- chmod +x goreleaser
- ./scripts/version.sh ./goreleaser --snapshot --skip-publish --clean
trigger:
event:

3
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1,3 @@
github: hrfee
ko_fi: hrfee
custom: https://www.buymeacoffee.com/hrfee

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**
@@ -19,7 +19,10 @@ What to do to reproduce the problem.
**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.
**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` 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.

12
.gitignore vendored
View File

@@ -1,4 +1,7 @@
node_modules/
site/node_modules/
site/out/
site/tempts/
mail/*.html
dist/
build/
@@ -12,3 +15,12 @@ config-payload.json
server.key
server.pem
server.crt
instructions-debian.txt
cl.md
./telegram/
mautrix/
tempts/
matacc.txt
scripts/langmover/lang
scripts/langmover/lang2
scripts/langmover/out

View File

@@ -8,50 +8,170 @@ before:
hooks:
- go mod download
- rm -rf data/web
- mkdir -p data
- cp -r static data/web
- cp -r css data/web/
- mkdir -p data/web/css
- bash -c 'cp -r static/* data/web/'
- npm install
- cp node_modules/a17t/dist/a17t.css data/web/css/
- npm install esbuild
- 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/
- python3 config/fixconfig.py -i config/config-base.json -o data/config-base.json
- python3 config/generate_ini.py -i config/config-base.json -o data/config-default.ini
- python3 mail/generate.py -o data/
- python3 version.py {{.Version}} version.go
- bash -c 'npx esbuild ts/*.ts ts/modules/*.ts --outdir=./data/web/js/ --minify'
- go get -u github.com/swaggo/swag/cmd/swag
- 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/
- rm -rf tempts
- cp -r ts tempts
- scripts/dark-variant.sh tempts
- scripts/dark-variant.sh tempts/modules
- mkdir -p data/web/js
- npx esbuild --target=es6 --bundle tempts/admin.ts --outfile=./data/web/js/admin.js --minify
- npx esbuild --target=es6 --bundle tempts/user.ts --outfile=./data/web/js/user.js --minify
- npx esbuild --target=es6 --bundle tempts/pwr.ts --outfile=./data/web/js/pwr.js --minify
- npx esbuild --target=es6 --bundle tempts/form.ts --outfile=./data/web/js/form.js --minify
- npx esbuild --target=es6 --bundle tempts/setup.ts --outfile=./data/web/js/setup.js --minify
- npx esbuild --target=es6 --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 --external:../fonts/hanken* --minify
- cp html/crash.html data/
- 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 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:
- dir: ./
- 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 -X main.cssVersion={{.Env.JFA_GO_CSS_VERSION}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
goos:
- linux
- windows
- darwin
goarch:
- amd64
- arm
- arm64
- amd64
- id: windows-tray
dir: ./
env:
- CGO_ENABLED=1
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
flags:
- -tags=tray
ldflags:
- -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}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}" -H=windowsgui
goos:
- windows
goarch:
- amd64
- id: linux-tray
dir: ./
env:
- CGO_ENABLED=1
flags:
- -tags=tray
ldflags:
- -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}} -X main.buildTimeUnix={{.Env.JFA_GO_BUILD_TIME}} -X main.builtBy="{{.Env.JFA_GO_BUILT_BY}}"
goos:
- linux
goarch:
- amd64
archives:
- replacements:
darwin: Darwin
linux: Linux
windows: Windows
amd64: x86_64
files:
- data/*
- data/**/*
- data/**/**/*
- id: windows-tray
builds:
- windows-tray
format: zip
name_template: >-
{{ .ProjectName }}_{{ .Version }}_TrayIcon_
{{- if eq .Os "darwin" }}macOS
{{- else }}{{- title .Os }}{{ end }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else }}{{ .Arch }}{{ end }}
- id: linux-tray
builds:
- linux-tray
format: zip
name_template: >-
{{ .ProjectName }}_{{ .Version }}_TrayIcon_
{{- if eq .Os "darwin" }}macOS
{{- else }}{{- title .Os }}{{ end }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else }}{{ .Arch }}{{ end }}
- id: notray
builds:
- notray
format: zip
name_template: >-
{{ .ProjectName }}_{{ .Version }}_
{{- if eq .Os "darwin" }}macOS
{{- else }}{{- title .Os }}{{ end }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else }}{{ .Arch }}{{ end }}
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "git-{{.ShortCommit}}"
name_template: "0.0.0-{{ .Env.JFA_GO_NFPM_EPOCH }}"
changelog:
sort: asc
filters:
exclude:
- '^docs:'
- '^test:'
nfpms:
- id: notray
file_name_template: '{{ .ProjectName }}{{ if .IsSnapshot }}-git{{ end }}_{{ .Arch }}_{{ if .IsSnapshot }}{{ .ShortCommit }}{{ else }}v{{ .Version }}{{ end }}'
package_name: jfa-go
homepage: https://github.com/hrfee/jfa-go
description: A web app for managing users on Jellyfin
maintainer: Harvey Tindall <hrfee@hrfee.dev>
license: MIT
vendor: hrfee.dev
version_metadata: git
builds:
- notray
contents:
- src: ./LICENSE
dst: /usr/share/licenses/jfa-go
formats:
- apk
- deb
- rpm
- id: tray
file_name_template: '{{ .ProjectName }}{{ if .IsSnapshot }}-git{{ end }}_TrayIcon_{{ .Arch }}_{{ if .IsSnapshot }}{{ .ShortCommit }}{{ else }}v{{ .Version }}{{ end }}'
package_name: jfa-go-tray
homepage: https://github.com/hrfee/jfa-go
description: A web app for managing users on Jellyfin
maintainer: Harvey Tindall <hrfee@hrfee.dev>
license: MIT
vendor: hrfee.dev
version_metadata: git
builds:
- linux-tray
contents:
- src: ./LICENSE
dst: /usr/share/licenses/jfa-go
formats:
- apk
- deb
- rpm
overrides:
deb:
conflicts:
- jfa-go
replaces:
- jfa-go
dependencies:
- libayatana-appindicator
rpm:
dependencies:
- libappindicator-gtk3
apk:
dependencies:
- libayatana-appindicator

View File

@@ -1,8 +1,44 @@
#### Code
---
title: "Building/Contributing for developers"
date: 2021-07-25T00:33:36+01:00
draft: false
---
# Code
I use 4 spaces for indentation. Go should ideally be formatted with `goimports` and/or `gofmt`. I don't use a formatter on typescript, so don't worry about that.
If you need to test your changes:
* `make debug` will build everything, and include sourcemaps for typescript. This should be the first thing you run.
* `make compile` compiles go into `build/jfa-go`.
* `make ts-debug` will compile typescript w/ sourcemaps into `build/data/web/js`.
* `make copy` will copy css, html, language and static files into `build/data`.
Code in Go should ideally use `PascalCase` for exported values, and `camelCase` for non-exported, JSON for transferring data should use `snake_case`, and Typescript should use `camelCase`. Forgive me for my many inconsistencies in this, and feel free to fix them if you want.
Functions in Go that need to access `*appContext` should be generally be receivers, except when the behaviour could be seen as somewhat independent from it (`email.go` is the best example, its behaviour is broadly independent from the main app except from a couple config values).
# Compiling
The Makefile is more suited towards development than other build methods, and provides separate build stages to speed up compilation when only making changes to specific aspects of the project.
Prefix each of these with `make DEBUG=on `:
* `all` will download deps and build everything. The executable and data will be placed in `build`. This is only necessary the first time.
* `npm` will download all node.js build-time dependencies.
* `compile` will only compile go code into the `build/jfa-go` executable.
* `typescript` will compile typescript w/ sourcemaps into `build/data/web/js`.
* `bundle-css` will bundle CSS and place it in `build/data/web/css`.
* `inline` will inline the css and javascript used in the single-file crash report webpage.
* `configuration` will generate the `config-base.json` (used to render settings in the web ui) and `config-default.ini` and put them in `build/data`.
* `email` will compile email mjml, and copy the text versions in to `build/data`.
* `swagger`: generates swagger documentation for the API.
* `copy` will copy iconography, html, language files and static data into `build/data`.
## Environment variables
* `DEBUG=on/off`: If on, compiles with type-checking for typescript, sourcemaps, non-minified css and no symbol stripping.
* `INTERNAL=on/off`: Whether or not to embed file assets into the binary itself, or store them separately beside the binary.
* `UPDATER=on/off/docker`: Enable/Disable the updater, or set a special update type (currently only docker, which disables self-updating the binary).
* `TRAY=on/off`: Enable/disable the tray icon, which lets you start/stop/autostart on login. For linux, requires `libappindicator3-dev` for debian or the equivalent on other distributions.
* `GOESBUILD=on`: Use a locally installed `esbuild` binary. NPM doesn't provide builds for all os/architectures, so `npx esbuild` might not work for you, so the binary is compiled/installed with `go get`.
* `GOBINARY=<path to go>`: Alternative path to go executable. Useful for testing with unstable go releases.
* `VERSION=v<semver>`: Alternative verision number, useful to test update functionality.
* `COMMIT=<short commit>`: Self explanatory.
* `LDFLAGS=<ldflags>`: Passed to `go build -ldflags`.
* `E2EE=on/off`: Enable/disable end-to-end encryption support for Matrix, which is currently very broken. Must subsequently be enabled (with Advanced settings enabled) in Settings > Matrix.
* `TAGS=<tags>`: Passed to `go build -tags`.
* `OS=<os>`: Unrelated to GOOS, if set to `windows`, `-H=windowsgui` is passed to ldflags, which stops a windows terminal popping up when run.
* `RACE=on/off`: If on, compiles with the go race detector included.

View File

@@ -1,19 +1,29 @@
FROM golang:latest AS build
FROM --platform=$BUILDPLATFORM golang:latest AS support
COPY . /opt/build
RUN apt update -y \
&& apt install build-essential python3-pip curl software-properties-common sed upx -y \
&& (curl -sL https://deb.nodesource.com/setup_14.x | bash -) \
&& apt install nodejs \
&& (cd /opt/build; make all; make compress) \
&& sed -i 's#id="pwrJfPath" placeholder="Folder"#id="pwrJfPath" value="/jf" disabled#g' /opt/build/build/data/html/setup.html
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_current.x | bash -) \
&& apt-get install nodejs \
&& (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
FROM --platform=$BUILDPLATFORM golang:latest AS build
ARG TARGETARCH
ENV GOARCH=$TARGETARCH
COPY --from=support /opt/build /opt/build
RUN (cd /opt/build; make compile INTERNAL=off UPDATER=docker)
FROM golang:latest
COPY --from=build /opt/build/build /opt/jfa-go
EXPOSE 8056
EXPOSE 8057
CMD [ "/opt/jfa-go/jfa-go", "-data", "/data" ]

View File

@@ -1,6 +1,8 @@
---jfa-go---
MIT License
Copyright (c) 2020 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
@@ -19,3 +21,4 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

200
Makefile
View File

@@ -1,65 +1,193 @@
GOESBUILD ?= off
ifeq ($(GOESBUILD), on)
ESBUILD := esbuild
else
ESBUILD := npx esbuild
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)
BUILDTIME ?= $(shell date +%s)
UPDATER ?= off
LDFLAGS := -X main.version=$(VERSION) -X main.commit=$(COMMIT) -X main.cssVersion=$(CSSVERSION) -X main.buildTimeUnix=$(BUILDTIME) $(if $(BUILTBY),-X 'main.builtBy=$(BUILTBY)',)
ifeq ($(UPDATER), on)
LDFLAGS := $(LDFLAGS) -X main.updater=binary
else ifneq ($(UPDATER), off)
LDFLAGS := $(LDFLAGS) -X main.updater=$(UPDATER)
endif
INTERNAL ?= on
TRAY ?= off
E2EE ?= off
TAGS := -tags "
ifeq ($(INTERNAL), on)
DATA := data
else
DATA := build/data
TAGS := $(TAGS) external
endif
ifeq ($(TRAY), on)
TAGS := $(TAGS) tray
endif
ifeq ($(E2EE), on)
TAGS := $(TAGS) e2ee
endif
TAGS := $(TAGS)"
OS := $(shell go env GOOS)
ifeq ($(TRAY)$(OS), onwindows)
LDFLAGS := $(LDFLAGS) -H=windowsgui
endif
DEBUG ?= off
ifeq ($(DEBUG), on)
SOURCEMAP := --sourcemap
TYPECHECK := npx tsc -noEmit --project ts/tsconfig.json
# jank
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 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
ifeq ($(RACE), on)
RACEDETECTOR := -race
else
RACEDETECTOR :=
endif
ifeq (, $(shell which esbuild))
ESBUILDINSTALL := go install github.com/evanw/esbuild/cmd/esbuild@latest
else
ESBUILDINSTALL :=
endif
ifeq ($(GOESBUILD), on)
NPMIGNOREOPTIONAL := --no-optional
NPMOPTS := $(NPMIGNOREOPTIONAL); $(ESBUILDINSTALL)
else
NPMOPTS :=
endif
npm:
$(info installing npm dependencies)
npm install
npm install $(NPMOPTS)
configuration:
$(info Fixing config-base)
-mkdir -p build/data
python3 config/fixconfig.py -i config/config-base.json -o build/data/config-base.json
-mkdir -p $(DATA)
python3 scripts/enumerate_config.py -i config/config-base.json -o $(DATA)/config-base.json
$(info Generating config-default.ini)
python3 config/generate_ini.py -i config/config-base.json -o build/data/config-default.ini
python3 scripts/generate_ini.py -i config/config-base.json -o $(DATA)/config-default.ini
email:
$(info Generating email html)
python3 mail/generate.py -o build/data/
python3 scripts/compile_mjml.py -o $(DATA)/
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 build/data/web/js
-npx esbuild ts/*.ts ts/modules/*.ts --outdir=./build/data/web/js/
ts-debug:
$(info compiling typescript w/ sourcemaps)
-mkdir -p build/data/web/js
-npx esbuild ts/*.ts ts/modules/*.ts --sourcemap --outdir=./build/data/web/js/
-rm -r build/data/web/js/ts
$(info copying typescript)
cp -r ts build/data/web/js
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/user.ts $(SOURCEMAP) --outfile=./$(DATA)/web/js/user.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:
go get github.com/swaggo/swag/cmd/swag
$(GOBINARY) install github.com/swaggo/swag/cmd/swag@latest
swag init -g main.go
version:
python3 version.py auto version.go
compile:
$(info Downloading deps)
go mod download
$(GOBINARY) mod download
$(info Building)
mkdir -p build
CGO_ENABLED=0 go build -o build/jfa-go *.go
$(GOBINARY) build $(RACEDETECTOR) -ldflags="$(LDFLAGS)" $(TAGS) -o build/jfa-go
compress:
upx --lzma build/jfa-go
copy:
$(info copying css)
-mkdir -p build/data/web/css
cp -r css build/data/web/
cp node_modules/a17t/dist/a17t.css build/data/web/css/
cp -r node_modules/remixicon/fonts/remixicon.css node_modules/remixicon/fonts/remixicon.woff2 build/data/web/css/
$(info copying html)
cp -r html build/data/
$(info copying static data)
-mkdir -p build/data/web
cp -r static/* build/data/web/
$(info copying language files)
cp -r lang build/data/
bundle-css:
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 --external:../fonts/hanken* --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
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
#
# external-files:
# python3 scripts/embed.py external
# -mkdir -p build
# $(info copying internal data into build/)
# cp -r data build/
install:
cp -r build $(DESTDIR)/jfa-go
all: configuration npm email version typescript swagger compile copy
debug: configuration npm email version ts-debug swagger compile copy
clean:
-rm -r $(DATA)
-rm -r build
-rm mail/*.html
-rm docs/docs.go docs/swagger.json docs/swagger.yaml
go clean
quick: configuration typescript variants-html bundle-css inline-css copy compile
all: configuration npm email typescript variants-html bundle-css inline-css swagger copy compile

142
README.md
View File

@@ -1,27 +1,52 @@
![jfa-go](images/banner.svg)
[![Build Status](https://drone.hrfee.pw/api/badges/hrfee/jfa-go/status.svg?ref=refs/heads/main)](https://drone.hrfee.pw/hrfee/jfa-go)
[![Build Status](https://drone.hrfee.dev/api/badges/hrfee/jfa-go/status.svg?ref=refs/heads/main)](https://drone.hrfee.dev/hrfee/jfa-go)
[![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.hrfee.pw/widgets/jfa-go/-/svg-badge.svg)](https://weblate.hrfee.pw/engage/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: Active-ish
Studies mean I can't work on this project a lot outside of breaks, however I hope i'll be able to fit in general support and things like bug fixes into my time. New features and such will likely come in short bursts throughout the year (if they do at all).
#### Does/Will it still work?
jfa-go currently works on Jellyfin 10.8.9, the latest version. I should be able to maintain compatability in the future, unless any big changes occur.
#### Alternatives
If you want a bit more of a guarantee of support, I've seen these projects mentioned although haven't tried them myself.
* [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.
I chose to rewrite the python [jellyfin-accounts](https://github.com/hrfee/jellyfin-accounts) in Go mainly as a learning experience, but also to slightly improve speeds and efficiency.
a rewrite of [jellyfin-accounts](https://github.com/hrfee/jellyfin-accounts) (original naming for both, ik
😂).
#### Features
* 🧑 Invite based account creation: Sends invites to your friends or family, and let them choose their own username and password without relying on you.
* 🧑 Invite based account creation: Send invites to your friends or family, and let them choose their own username and password without relying on you.
* Send invites via a link and/or email
* 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 user's 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.
* 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
* 🌓 Customizable look
* 🌓 Customizations
* Customize emails with variables and markdown
* Specify contact and help messages to appear in emails and pages
* Light and dark themes available
@@ -31,58 +56,106 @@ I chose to rewrite the python [jellyfin-accounts](https://github.com/hrfee/jelly
</p>
<p align="center">
<img src="images/invites.png" width="48%" style="margin-left: 1.5%;" alt="Invites tab"></img>
<img src="images/accounts.png" width="48%" style="margin-right: 1.5%;" alt="Accounts tab"></img>
<img src="images/invites.png" width="31%" style="margin-left: 1.5%;" alt="Invites tab"></img>
<img src="images/accounts.png" width="31%" style="margin-right: 1.5%;" alt="Accounts tab"></img>
<img src="images/create.png" width="31%" style="margin-right: 1.5%;" alt="Accounts creation"></img>
</p>
#### Install
Available on the AUR as [jfa-go](https://aur.archlinux.org/packages/jfa-go/) or [jfa-go-git](https://aur.archlinux.org/packages/jfa-go-git/).
**Note**: `TrayIcon` builds include a tray icon to start/stop/restart, and an option to automatically start when you log-in to your computer. For Linux users, these builds depend on the `libappindicator3-1`/`libappindicator-gtk3`/`libappindicator` package for Debian/Ubuntu, Fedora, and Alpine respectively.
For other platforms, grab an archive from the release section for your platform (or nightly builds [here](https://builds.hrfee.pw/view/hrfee/jfa-go)), and extract `jfa-go` and `data` to the same directory.
* For linux users, you can place them inside `/opt/jfa-go` and then run
`sudo ln -s /opt/jfa-go/jfa-go /usr/bin/jfa-go` to place it in your PATH.
Run the executable to start.
For [docker](https://hub.docker.com/repository/docker/hrfee/jfa-go), run:
```
##### [Docker](https://hub.docker.com/r/hrfee/jfa-go)
```sh
docker create \
--name "jfa-go" \ # Whatever you want to name it
-p 8056:8056 \
# -p 8057:8057 if using tls
-v /path/to/.config/jfa-go:/data \ # Path to wherever you want to store the config file and other data
-v /path/to/jellyfin:/jf \ # Path to jellyfin config directory
-v /path/to/jellyfin:/jf \ # Path to Jellyfin config directory, ignore if using Emby
-v /etc/localtime:/etc/localtime:ro \ # Makes sure time is correct
hrfee/jfa-go # hrfee/jfa-go:unstable for latest build from git
```
##### [Debian/Ubuntu](https://apt.hrfee.dev)
```sh
sudo apt-get update && sudo apt-get install curl apt-transport-https gnupg
curl https://apt.hrfee.dev/hrfee.pubkey.gpg | sudo apt-key add -
# For stable releases
echo "deb https://apt.hrfee.dev trusty main" | sudo tee /etc/apt/sources.list.d/hrfee.list
# ------
# For unstable releases
echo "deb https://apt.hrfee.dev trusty-unstable main" | sudo tee /etc/apt/sources.list.d/hrfee.list
# ------
sudo apt-get update
# For servers
sudo apt-get install jfa-go
# ------
# For desktops/servers with GUI (has dependencies)
sudo apt-get install jfa-go-tray
# ------
```
##### Arch
Available on the AUR as:
* [jfa-go](https://aur.archlinux.org/packages/jfa-go/) (stable)
* [jfa-go-bin](https://aur.archlinux.org/packages/jfa-go) (pre-compiled, stable)
* [jfa-go-git](https://aur.archlinux.org/packages/jfa-go-git/) (nightly)
##### Other platforms
Download precompiled binaries from:
* [The releases section](https://github.com/hrfee/jfa-go/releases) (stable)
* [Buildrone](https://builds.hrfee.dev/view/hrfee/jfa-go) (nightly)
unzip the `jfa-go`/`jfa-go.exe` executable to somewhere useful.
* For \*nix/macOS users, `chmod +x jfa-go` then place it somewhere in your PATH like `/usr/bin`.
Run the executable to start.
#### Build from source
If you're using docker, a Dockerfile is provided that builds from source.
Otherwise, full build instructions can be found [here](https://github.com/hrfee/jfa-go/wiki/Build).
Otherwise, full build instructions can be found [here](https://wiki.jfa-go.com/docs/build/).
#### Usage
Simply run `jfa-go` to start the application. A setup wizard will start on `localhost:8056` (or your own specified address). Upon completion, refresh the page.
Note: jfa-go does not run as a daemon by default. You'll need to figure this out yourself.
```
Usage of ./jfa-go:
-config string
alternate path to config file. (default "~/.config/jfa-go/config.ini")
-data string
alternate path to data directory. (default "~/.config/jfa-go")
Usage of jfa-go:
start
start jfa-go as a daemon and run in the background.
stop
stop a daemonized instance of jfa-go.
systemd
generate a systemd .service file.
-config, -c string
alternate path to config file. (default "/home/hrfee/.config/jfa-go/config.ini")
-data, -d string
alternate path to data directory. (default "/home/hrfee/.config/jfa-go")
-debug
Enables debug logging and exposes pprof.
Enables debug logging.
-help, -h
prints this message.
-host string
alternate address to host web ui on.
-port int
-port, -p int
alternate port to host web ui on.
-pprof
Exposes pprof profiler on /debug/pprof.
-swagger
Enable swagger at /swagger/index.html
```
#### Systemd
jfa-go does not run as a daemon by default. Run `jfa-go systemd` to create a systemd `.service` file in your current directory, which you can copy into `~/.config/systemd/user` or somewhere else.
---
If you're switching from jellyfin-accounts, copy your existing `~/.jf-accounts` to:
* `XDG_CONFIG_DIR/jfa-go` (usually ~/.config/jfa-go) on \*nix systems,
@@ -92,8 +165,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.hrfee.pw/widgets/jfa-go/-/multi-auto.svg)](https://weblate.hrfee.pw/engage/jfa-go/)
[![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.hrfee.pw/engage/jfa-go/). You can login with github.
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)

View File

@@ -1,38 +0,0 @@
This branch is for experimenting with [a17t](https://a17t.miles.land/) to replace bootstrap. Page structure is pretty much done (except setup.html), so i'm currently integrating this with the main app and existing web code.
#### todo
**general**
* [x] modal implementation
* [x] animations
* [x] utilities
* [x] CSS for light & dark
**admin**
* [x] invites tab
* [x] accounts tab
* [x] settings tab
* [x] modals
* [ ] integration with existing code
**invites**
* [x] page design
* [ ] integration with existing code
#### screenshots
##### dark
<p>
<img src="images/dark/invites.png" alt="invites" style="width: 32%; height: auto;">
<img src="images/dark/accounts.png" alt="accounts" style="width: 32%; height: auto;">
<img src="images/dark/settings.png" alt="settings" style="width: 32%; height: auto;">
<img src="images/dark/login-modal.png" alt="login modal" style="width: 32%; height: auto;">
<img src="images/dark/modify-settings.png" alt="modify user settings modal" style="width: 32%; height: auto;">
</p>
##### light
<p>
<img src="images/light/invites.png" alt="invites" style="width: 32%; height: auto;">
<img src="images/light/accounts.png" alt="accounts" style="width: 32%; height: auto;">
<img src="images/light/settings.png" alt="settings" style="width: 32%; height: auto;">
<img src="images/light/login-modal.png" alt="login modal" style="width: 32%; height: auto;">
<img src="images/light/modify-settings.png" alt="modify user settings modal" style="width: 32%; height: auto;">
</p>

409
api-invites.go Normal file
View File

@@ -0,0 +1,409 @@
package main
import (
"fmt"
"strconv"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"github.com/itchyny/timefmt-go"
"github.com/lithammer/shortuuid/v3"
"github.com/timshannon/badgerhold/v4"
)
func (app *appContext) checkInvites() {
currentTime := time.Now()
for _, data := range app.storage.GetInvites() {
expiry := data.ValidTill
if !currentTime.After(expiry) {
continue
}
app.debug.Printf("Housekeeping: Deleting old invite %s", data.Code)
notify := data.Notify
if emailEnabled && app.config.Section("notifications").Key("enabled").MustBool(false) && len(notify) != 0 {
app.debug.Printf("%s: Expiry notification", data.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(data.Code, data, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct expiry notification: %v", data.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", data.Code, err)
} else {
app.info.Printf("Sent expiry notification to %s", addr)
}
}
}(address)
}
wait.Wait()
}
app.storage.DeleteInvitesKey(data.Code)
}
}
func (app *appContext) checkInvite(code string, used bool, username string) bool {
currentTime := time.Now()
inv, match := app.storage.GetInvitesKey(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()
}
match = false
app.storage.DeleteInvitesKey(code)
} else if used {
del := false
newInv := inv
if newInv.RemainingUses == 1 {
del = true
app.storage.DeleteInvitesKey(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.SetInvitesKey(code, newInv)
}
}
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")
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.GetProfileKey(req.Profile); ok {
invite.Profile = req.Profile
} else {
invite.Profile = "Default"
}
}
app.storage.SetInvitesKey(inviteCode, invite)
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.checkInvites()
var invites []inviteDTO
for _, inv := range app.storage.GetInvites() {
_, months, days, hours, minutes, _ := timeDiff(inv.ValidTill, currentTime)
invite := inviteDTO{
Code: inv.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 {
// app.err.Printf("%s has notify section: %+v, you are %s\n", inv.Code, inv.Notify, gc.GetString("jfId"))
var addressOrID string
if app.config.Section("ui").Key("jellyfin_login").MustBool(false) {
addressOrID = gc.GetString("jfId")
} else {
addressOrID = app.config.Section("ui").Key("email").String()
}
if _, ok := inv.Notify[addressOrID]; ok {
if _, ok = inv.Notify[addressOrID]["notify-expiry"]; ok {
invite.NotifyExpiry = inv.Notify[addressOrID]["notify-expiry"]
}
if _, ok = inv.Notify[addressOrID]["notify-creation"]; ok {
invite.NotifyCreation = inv.Notify[addressOrID]["notify-creation"]
}
}
}
invites = append(invites, invite)
}
fullProfileList := app.storage.GetProfiles()
profiles := make([]string, len(fullProfileList))
if len(profiles) != 0 {
defaultProfile := app.storage.GetDefaultProfile()
profiles[0] = defaultProfile.Name
i := 1
if len(fullProfileList) > 1 {
app.storage.db.ForEach(badgerhold.Where("Name").Ne(profiles[0]), func(p *Profile) error {
profiles[i] = p.Name
i++
return nil
})
}
}
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.GetProfileKey(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.GetInvitesKey(req.Invite)
inv.Profile = req.Profile
app.storage.SetInvitesKey(req.Invite, inv)
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)
invite, ok := app.storage.GetInvitesKey(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.SetInvitesKey(code, invite)
}
}
}
// @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.GetInvitesKey(req.Code)
if ok {
app.storage.DeleteInvitesKey(req.Code)
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)
}

737
api-messages.go Normal file
View File

@@ -0,0 +1,737 @@
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) GetCustomContent(gc *gin.Context) {
lang := gc.Query("lang")
if _, ok := app.storage.lang.Email[lang]; !ok {
lang = app.storage.lang.chosenEmailLang
}
adminLang := lang
if _, ok := app.storage.lang.Admin[lang]; !ok {
adminLang = app.storage.lang.chosenAdminLang
}
list := emailListDTO{
"UserCreated": {Name: app.storage.lang.Email[lang].UserCreated["name"], Enabled: app.storage.MustGetCustomContentKey("UserCreated").Enabled},
"InviteExpiry": {Name: app.storage.lang.Email[lang].InviteExpiry["name"], Enabled: app.storage.MustGetCustomContentKey("InviteExpiry").Enabled},
"PasswordReset": {Name: app.storage.lang.Email[lang].PasswordReset["name"], Enabled: app.storage.MustGetCustomContentKey("PasswordReset").Enabled},
"UserDeleted": {Name: app.storage.lang.Email[lang].UserDeleted["name"], Enabled: app.storage.MustGetCustomContentKey("UserDeleted").Enabled},
"UserDisabled": {Name: app.storage.lang.Email[lang].UserDisabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserDisabled").Enabled},
"UserEnabled": {Name: app.storage.lang.Email[lang].UserEnabled["name"], Enabled: app.storage.MustGetCustomContentKey("UserEnabled").Enabled},
"InviteEmail": {Name: app.storage.lang.Email[lang].InviteEmail["name"], Enabled: app.storage.MustGetCustomContentKey("InviteEmail").Enabled},
"WelcomeEmail": {Name: app.storage.lang.Email[lang].WelcomeEmail["name"], Enabled: app.storage.MustGetCustomContentKey("WelcomeEmail").Enabled},
"EmailConfirmation": {Name: app.storage.lang.Email[lang].EmailConfirmation["name"], Enabled: app.storage.MustGetCustomContentKey("EmailConfirmation").Enabled},
"UserExpired": {Name: app.storage.lang.Email[lang].UserExpired["name"], Enabled: app.storage.MustGetCustomContentKey("UserExpired").Enabled},
"UserLogin": {Name: app.storage.lang.Admin[adminLang].Strings["userPageLogin"], Enabled: app.storage.MustGetCustomContentKey("Login").Enabled},
"UserPage": {Name: app.storage.lang.Admin[adminLang].Strings["userPagePage"], Enabled: app.storage.MustGetCustomContentKey("Page").Enabled},
}
filter := gc.Query("filter")
if filter == "user" {
list = emailListDTO{"UserLogin": list["UserLogin"], "UserPage": list["UserPage"]}
} else {
delete(list, "UserLogin")
delete(list, "UserPage")
}
gc.JSON(200, list)
}
// No longer needed, these are stored by string keys in the database now.
/* func (app *appContext) getCustomMessage(id string) *CustomContent {
switch id {
case "Announcement":
return &CustomContent{}
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
case "UserLogin":
return &app.storage.userPage.Login
case "UserPage":
return &app.storage.userPage.Page
}
return nil
} */
// @Summary Sets the corresponding custom content.
// @Produce json
// @Param CustomContent body CustomContent true "Content = email (in markdown)."
// @Success 200 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param id path string true "ID of content"
// @Router /config/emails/{id} [post]
// @Security Bearer
// @tags Configuration
func (app *appContext) SetCustomMessage(gc *gin.Context) {
var req CustomContent
gc.BindJSON(&req)
id := gc.Param("id")
if req.Content == "" {
respondBool(400, false, gc)
return
}
message, ok := app.storage.GetCustomContentKey(id)
if !ok {
respondBool(400, false, gc)
return
}
message.Content = req.Content
message.Enabled = true
app.storage.SetCustomContentKey(id, message)
respondBool(200, true, gc)
}
// @Summary Enable/Disable custom content.
// @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) SetCustomMessageState(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)
}
message, ok := app.storage.GetCustomContentKey(id)
if !ok {
respondBool(400, false, gc)
return
}
message.Enabled = enabled
app.storage.SetCustomContentKey(id, message)
respondBool(200, true, gc)
}
// @Summary Returns the custom content/message (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) GetCustomMessageTemplate(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")
customMessage, ok := app.storage.GetCustomContentKey(id)
if !ok {
app.err.Printf("Failed to get custom message with ID \"%s\"", id)
respondBool(400, false, gc)
return
}
if id == "WelcomeEmail" {
conditionals = []string{"{yourAccountWillExpire}"}
customMessage.Conditionals = conditionals
} else if id == "UserPage" {
variables = []string{"{username}"}
customMessage.Variables = variables
} else if id == "UserLogin" {
variables = []string{}
customMessage.Variables = variables
}
content = customMessage.Content
noContent := content == ""
if !noContent {
variables = customMessage.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)
case "UserLogin", "UserPage":
values = map[string]interface{}{}
}
if err != nil {
respondBool(500, false, gc)
return
}
if noContent && id != "Announcement" && id != "UserPage" && id != "UserLogin" {
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++
}
}
customMessage.Variables = variables
}
if variables == nil {
variables = []string{}
}
app.storage.SetCustomContentKey(id, customMessage)
var mail *Message
if id != "UserLogin" && id != "UserPage" {
mail, err = app.email.constructTemplate("", "<div class=\"preview-content\"></div>", app)
if err != nil {
respondBool(500, false, gc)
return
}
} else {
mail = &Message{
HTML: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
Markdown: "<div class=\"card ~neutral dark:~d_neutral @low preview-content\"></div>",
}
}
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
}
tgToken, ok := app.telegram.TokenVerified(req.Token)
app.telegram.DeleteVerifiedToken(req.Token)
if !ok {
respondBool(500, false, gc)
return
}
tgUser := TelegramUser{
ChatID: tgToken.ChatID,
Username: tgToken.Username,
Contact: true,
}
if lang, ok := app.telegram.languages[tgToken.ChatID]; ok {
tgUser.Lang = lang
}
app.storage.SetTelegramKey(req.ID, tgUser)
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
}
app.setContactMethods(req, gc)
}
func (app *appContext) setContactMethods(req SetContactMethodsDTO, gc *gin.Context) {
if tgUser, ok := app.storage.GetTelegramKey(req.ID); ok {
change := tgUser.Contact != req.Telegram
tgUser.Contact = req.Telegram
app.storage.SetTelegramKey(req.ID, tgUser)
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.GetDiscordKey(req.ID); ok {
change := dcUser.Contact != req.Discord
dcUser.Contact = req.Discord
app.storage.SetDiscordKey(req.ID, dcUser)
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.GetMatrixKey(req.ID); ok {
change := mxUser.Contact != req.Matrix
mxUser.Contact = req.Matrix
app.storage.SetMatrixKey(req.ID, mxUser)
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.GetEmailsKey(req.ID); ok {
change := email.Contact != req.Email
email.Contact = req.Email
app.storage.SetEmailsKey(req.ID, email)
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")
_, ok := app.telegram.TokenVerified(pin)
respondBool(200, ok, 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.GetInvitesKey(code); !ok {
respondBool(401, false, gc)
return
}
pin := gc.Param("pin")
token, ok := app.telegram.TokenVerified(pin)
if ok && app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(token.Username) {
app.discord.DeleteVerifiedUser(pin)
respondBool(400, false, gc)
return
}
respondBool(200, ok, 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.GetInvitesKey(code); !ok {
respondBool(401, false, gc)
return
}
pin := gc.Param("pin")
user, ok := app.discord.UserVerified(pin)
if ok && app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(user.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.GetInvitesKey(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.GetInvitesKey(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.GetMatrix() {
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.GetInvitesKey(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.GetMatrix() == nil {
app.storage.deprecatedMatrix = matrixStore{}
}
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.SetMatrixKey(req.JellyfinID, MatrixUser{
UserID: req.UserID,
RoomID: string(roomID),
Lang: "en-us",
Contact: true,
Encrypted: encrypted,
})
app.matrix.isEncrypted[roomID] = encrypted
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.SetDiscordKey(req.JellyfinID, user)
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
} */
app.storage.DeleteDiscordKey(req.ID)
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
} */
app.storage.DeleteTelegramKey(req.ID)
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
} */
app.storage.DeleteMatrixKey(req.ID)
respondBool(200, true, gc)
}

109
api-ombi.go Normal file
View File

@@ -0,0 +1,109 @@
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.GetEmailsKey(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.GetProfileKey(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.SetProfileKey(profileName, profile)
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.GetProfileKey(profileName)
if !ok {
respondBool(400, false, gc)
return
}
profile.Ombi = nil
app.storage.SetProfileKey(profileName, profile)
respondBool(204, true, gc)
}

112
api-profiles.go Normal file
View File

@@ -0,0 +1,112 @@
package main
import (
"time"
"github.com/gin-gonic/gin"
"github.com/timshannon/badgerhold/v4"
)
// @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.debug.Println("Profiles requested")
out := getProfilesDTO{
DefaultProfile: app.storage.GetDefaultProfile().Name,
Profiles: map[string]profileDTO{},
}
for _, p := range app.storage.GetProfiles() {
out.Profiles[p.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.GetProfileKey(req.Name); !ok {
app.err.Printf("Profile not found: \"%s\"", req.Name)
respond(500, "Profile not found", gc)
return
}
app.storage.db.ForEach(&badgerhold.Query{}, func(profile *Profile) error {
if profile.Name == req.Name {
profile.Default = true
} else {
profile.Default = false
}
app.storage.SetProfileKey(profile.Name, *profile)
return nil
})
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.SetProfileKey(req.Name, profile)
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
app.storage.DeleteProfileKey(name)
respondBool(200, true, gc)
}

623
api-userpage.go Normal file
View File

@@ -0,0 +1,623 @@
package main
import (
"net/http"
"os"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
)
// @Summary Returns the logged-in user's Jellyfin ID & Username, and other details.
// @Produce json
// @Success 200 {object} MyDetailsDTO
// @Router /my/details [get]
// @tags User Page
func (app *appContext) MyDetails(gc *gin.Context) {
resp := MyDetailsDTO{
Id: gc.GetString("jfId"),
}
user, status, err := app.jf.UserByID(resp.Id, false)
if status != 200 || err != nil {
app.err.Printf("Failed to get Jellyfin user (%d): %+v\n", status, err)
respond(500, "Failed to get user", gc)
return
}
resp.Username = user.Name
resp.Admin = user.Policy.IsAdministrator
resp.AccountsAdmin = false
if !app.config.Section("ui").Key("allow_all").MustBool(false) {
adminOnly := app.config.Section("ui").Key("admin_only").MustBool(true)
if emailStore, ok := app.storage.GetEmailsKey(resp.Id); ok {
resp.AccountsAdmin = emailStore.Admin
}
resp.AccountsAdmin = resp.AccountsAdmin || (adminOnly && resp.Admin)
}
resp.Disabled = user.Policy.IsDisabled
if exp, ok := app.storage.GetUserExpiryKey(user.ID); ok {
resp.Expiry = exp.Expiry.Unix()
}
if emailEnabled {
resp.Email = &MyDetailsContactMethodsDTO{}
if email, ok := app.storage.GetEmailsKey(user.ID); ok && email.Addr != "" {
resp.Email.Value = email.Addr
resp.Email.Enabled = email.Contact
}
}
if discordEnabled {
resp.Discord = &MyDetailsContactMethodsDTO{}
if discord, ok := app.storage.GetDiscordKey(user.ID); ok {
resp.Discord.Value = RenderDiscordUsername(discord)
resp.Discord.Enabled = discord.Contact
}
}
if telegramEnabled {
resp.Telegram = &MyDetailsContactMethodsDTO{}
if telegram, ok := app.storage.GetTelegramKey(user.ID); ok {
resp.Telegram.Value = telegram.Username
resp.Telegram.Enabled = telegram.Contact
}
}
if matrixEnabled {
resp.Matrix = &MyDetailsContactMethodsDTO{}
if matrix, ok := app.storage.GetMatrixKey(user.ID); ok {
resp.Matrix.Value = matrix.UserID
resp.Matrix.Enabled = matrix.Contact
}
}
gc.JSON(200, resp)
}
// @Summary Sets whether to notify yourself 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 /my/contact [post]
// @Security Bearer
// @tags User Page
func (app *appContext) SetMyContactMethods(gc *gin.Context) {
var req SetContactMethodsDTO
gc.BindJSON(&req)
req.ID = gc.GetString("jfId")
if req.ID == "" {
respondBool(400, false, gc)
return
}
app.setContactMethods(req, gc)
}
// @Summary Logout by deleting refresh token from cookies.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 500 {object} stringResponse
// @Router /my/logout [post]
// @Security Bearer
// @tags User Page
func (app *appContext) LogoutUser(gc *gin.Context) {
cookie, err := gc.Cookie("user-refresh")
if err != nil {
app.debug.Printf("Couldn't get cookies: %s", err)
respond(500, "Couldn't fetch cookies", gc)
return
}
app.invalidTokens = append(app.invalidTokens, cookie)
gc.SetCookie("refresh", "invalid", -1, "/my", gc.Request.URL.Hostname(), true, true)
respondBool(200, true, gc)
}
// @Summary confirm an action (e.g. changing an email address.)
// @Produce json
// @Param jwt path string true "jwt confirmation code"
// @Router /my/confirm/{jwt} [post]
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 404
// @Success 303
// @Failure 500 {object} stringResponse
// @tags User Page
func (app *appContext) ConfirmMyAction(gc *gin.Context) {
app.confirmMyAction(gc, "")
}
func (app *appContext) confirmMyAction(gc *gin.Context, key string) {
var claims jwt.MapClaims
var target ConfirmationTarget
var id string
fail := func() {
gcHTML(gc, 404, "404.html", gin.H{
"cssClass": app.cssClass,
"cssVersion": cssVersion,
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
})
}
// Validate key
if key == "" {
key = gc.Param("jwt")
}
token, err := jwt.Parse(key, checkToken)
if err != nil {
app.err.Printf("Failed to parse key: %s", err)
fail()
// respond(500, "unknownError", gc)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
app.err.Printf("Failed to parse key: %s", err)
fail()
// respond(500, "unknownError", gc)
return
}
expiry := time.Unix(int64(claims["exp"].(float64)), 0)
if !(ok && token.Valid && claims["type"].(string) == "confirmation" && expiry.After(time.Now())) {
app.err.Printf("Invalid key")
fail()
// respond(400, "invalidKey", gc)
return
}
target = ConfirmationTarget(int(claims["target"].(float64)))
id = claims["id"].(string)
// Perform an Action
if target == NoOp {
gc.Redirect(http.StatusSeeOther, "/my/account")
return
} else if target == UserEmailChange {
emailStore, ok := app.storage.GetEmailsKey(id)
if !ok {
emailStore = EmailAddress{
Contact: true,
}
}
emailStore.Addr = claims["email"].(string)
app.storage.SetEmailsKey(id, emailStore)
if app.config.Section("ombi").Key("enabled").MustBool(false) {
ombiUser, code, err := app.getOmbiUser(id)
if code == 200 && err == nil {
ombiUser["emailAddress"] = claims["email"].(string)
code, err = app.ombi.ModifyUser(ombiUser)
if code != 200 || err != nil {
app.err.Printf("%s: Failed to change ombi email address (%d): %v", ombiUser["userName"].(string), code, err)
}
}
}
app.info.Println("Email list modified")
gc.Redirect(http.StatusSeeOther, "/my/account")
return
}
}
// @Summary Modify your email address.
// @Produce json
// @Param ModifyMyEmailDTO body ModifyMyEmailDTO true "New email address."
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 401 {object} stringResponse
// @Failure 500 {object} stringResponse
// @Router /my/email [post]
// @Security Bearer
// @tags User Page
func (app *appContext) ModifyMyEmail(gc *gin.Context) {
var req ModifyMyEmailDTO
gc.BindJSON(&req)
app.debug.Println("Email modification requested")
if !strings.ContainsRune(req.Email, '@') {
respond(400, "Invalid Email Address", gc)
return
}
id := gc.GetString("jfId")
// We'll use the ConfirmMyAction route to do the work, even if we don't need to confirm the address.
claims := jwt.MapClaims{
"valid": true,
"id": id,
"email": req.Email,
"type": "confirmation",
"target": UserEmailChange,
"exp": time.Now().Add(time.Hour).Unix(),
}
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
key, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
if err != nil {
app.err.Printf("Failed to generate confirmation token: %v", err)
respond(500, "errorUnknown", gc)
return
}
if emailEnabled && app.config.Section("email_confirmation").Key("enabled").MustBool(false) {
user, status, err := app.jf.UserByID(id, false)
name := ""
if status == 200 && err == nil {
name = user.Name
}
app.debug.Printf("%s: Email confirmation required", id)
respond(401, "confirmEmail", gc)
msg, err := app.email.constructConfirmation("", name, key, app, false)
if err != nil {
app.err.Printf("%s: Failed to construct confirmation email: %v", name, err)
} else if err := app.email.send(msg, req.Email); err != nil {
app.err.Printf("%s: Failed to send user confirmation email: %v", name, err)
} else {
app.info.Printf("%s: Sent user confirmation email to \"%s\"", name, req.Email)
}
return
}
app.confirmMyAction(gc, key)
return
}
// @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 /my/discord/invite [get]
// @Security Bearer
// @tags User Page
func (app *appContext) MyDiscordServerInvite(gc *gin.Context) {
if app.discord.inviteChannelName == "" {
respondBool(400, 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 Returns a linking PIN for discord/telegram
// @Produce json
// @Success 200 {object} GetMyPINDTO
// @Failure 400 {object} stringResponse
// Param service path string true "discord/telegram"
// @Router /my/pin/{service} [get]
// @Security Bearer
// @tags User Page
func (app *appContext) GetMyPIN(gc *gin.Context) {
service := gc.Param("service")
resp := GetMyPINDTO{}
switch service {
case "discord":
resp.PIN = app.discord.NewAssignedAuthToken(gc.GetString("jfId"))
break
case "telegram":
resp.PIN = app.telegram.NewAssignedAuthToken(gc.GetString("jfId"))
break
default:
respond(400, "invalid service", gc)
return
}
gc.JSON(200, resp)
}
// @Summary Returns true/false on whether or not your discord PIN was verified, and assigns the discord user to you.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Router /my/discord/verified/{pin} [get]
// @Security Bearer
// @tags User Page
func (app *appContext) MyDiscordVerifiedInvite(gc *gin.Context) {
pin := gc.Param("pin")
dcUser, ok := app.discord.AssignedUserVerified(pin, gc.GetString("jfId"))
app.discord.DeleteVerifiedUser(pin)
if !ok {
respondBool(200, false, gc)
return
}
if app.config.Section("discord").Key("require_unique").MustBool(false) && app.discord.UserExists(dcUser.ID) {
respondBool(400, false, gc)
return
}
existingUser, ok := app.storage.GetDiscordKey(gc.GetString("jfId"))
if ok {
dcUser.Lang = existingUser.Lang
dcUser.Contact = existingUser.Contact
}
app.storage.SetDiscordKey(gc.GetString("jfId"), dcUser)
respondBool(200, true, gc)
}
// @Summary Returns true/false on whether or not your telegram PIN was verified, and assigns the telegram user to you.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 401 {object} boolResponse
// @Param pin path string true "PIN code to check"
// @Router /my/telegram/verified/{pin} [get]
// @Security Bearer
// @tags User Page
func (app *appContext) MyTelegramVerifiedInvite(gc *gin.Context) {
pin := gc.Param("pin")
token, ok := app.telegram.AssignedTokenVerified(pin, gc.GetString("jfId"))
app.telegram.DeleteVerifiedToken(pin)
if !ok {
respondBool(200, false, gc)
return
}
if app.config.Section("telegram").Key("require_unique").MustBool(false) && app.telegram.UserExists(token.Username) {
respondBool(400, false, gc)
return
}
tgUser := TelegramUser{
ChatID: token.ChatID,
Username: token.Username,
Contact: true,
}
if lang, ok := app.telegram.languages[tgUser.ChatID]; ok {
tgUser.Lang = lang
}
existingUser, ok := app.storage.GetTelegramKey(gc.GetString("jfId"))
if ok {
tgUser.Lang = existingUser.Lang
tgUser.Contact = existingUser.Contact
}
app.storage.SetTelegramKey(gc.GetString("jfId"), tgUser)
respondBool(200, true, gc)
}
// @Summary Generate and send a new PIN to your given matrix user.
// @Produce json
// @Success 200 {object} boolResponse
// @Failure 400 {object} stringResponse
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Param MatrixSendPINDTO body MatrixSendPINDTO true "User's Matrix ID."
// @Router /my/matrix/user [post]
// @Security Bearer
// @tags User Page
func (app *appContext) MatrixSendMyPIN(gc *gin.Context) {
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.GetMatrix() {
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 your matrix PIN is valid, and link the account to yours if so.
// @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 /my/matrix/verified/{userID}/{pin} [get]
// @Security Bearer
// @tags User Page
func (app *appContext) MatrixCheckMyPIN(gc *gin.Context) {
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
}
mxUser := *user.User
mxUser.Contact = true
existingUser, ok := app.storage.GetMatrixKey(gc.GetString("jfId"))
if ok {
mxUser.Lang = existingUser.Lang
mxUser.Contact = existingUser.Contact
}
app.storage.SetMatrixKey(gc.GetString("jfId"), mxUser)
delete(app.matrix.tokens, pin)
respondBool(200, true, gc)
}
// @Summary unlink the Discord account from your Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Router /my/discord [delete]
// @Security Bearer
// @Tags User Page
func (app *appContext) UnlinkMyDiscord(gc *gin.Context) {
app.storage.DeleteDiscordKey(gc.GetString("jfId"))
respondBool(200, true, gc)
}
// @Summary unlink the Telegram account from your Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Router /my/telegram [delete]
// @Security Bearer
// @Tags User Page
func (app *appContext) UnlinkMyTelegram(gc *gin.Context) {
app.storage.DeleteTelegramKey(gc.GetString("jfId"))
respondBool(200, true, gc)
}
// @Summary unlink the Matrix account from your Jellyfin user. Always succeeds.
// @Produce json
// @Success 200 {object} boolResponse
// @Router /my/matrix [delete]
// @Security Bearer
// @Tags User Page
func (app *appContext) UnlinkMyMatrix(gc *gin.Context) {
app.storage.DeleteMatrixKey(gc.GetString("jfId"))
respondBool(200, true, gc)
}
// @Summary Generate & send a password reset link if the given username/email/contact method exists. Doesn't give you any info about it's success.
// @Produce json
// @Param address path string true "address/contact method associated w/ your account."
// @Success 204 {object} boolResponse
// @Failure 400 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /my/password/reset/{address} [post]
// @Tags User Page
func (app *appContext) ResetMyPassword(gc *gin.Context) {
// All requests should take 1 second, to make it harder to tell if a success occured or not.
timerWait := make(chan bool)
cancel := time.AfterFunc(1*time.Second, func() {
timerWait <- true
})
address := gc.Param("address")
if address == "" {
app.debug.Println("Ignoring empty request for PWR")
cancel.Stop()
respondBool(400, false, gc)
return
}
var pwr InternalPWR
var err error
jfUser, ok := app.ReverseUserSearch(address)
if !ok {
app.debug.Printf("Ignoring PWR request: User not found")
for range timerWait {
respondBool(204, true, gc)
return
}
return
}
pwr, err = app.GenInternalReset(jfUser.ID)
if err != nil {
app.err.Printf("Failed to get user from Jellyfin: %v", err)
for range timerWait {
respondBool(204, true, gc)
return
}
return
}
if app.internalPWRs == nil {
app.internalPWRs = map[string]InternalPWR{}
}
app.internalPWRs[pwr.PIN] = pwr
// FIXME: Send to all contact methods
msg, err := app.email.constructReset(
PasswordReset{
Pin: pwr.PIN,
Username: pwr.Username,
Expiry: pwr.Expiry,
Internal: true,
}, app, false,
)
if err != nil {
app.err.Printf("Failed to construct password reset message for \"%s\": %v", pwr.Username, err)
for range timerWait {
respondBool(204, true, gc)
return
}
return
} else if err := app.sendByID(msg, jfUser.ID); err != nil {
app.err.Printf("Failed to send password reset message to \"%s\": %v", address, err)
} else {
app.info.Printf("Sent password reset message to \"%s\"", address)
}
for range timerWait {
respondBool(204, true, gc)
return
}
}
// @Summary Change your password, given the old one and the new one.
// @Produce json
// @Param ChangeMyPasswordDTO body ChangeMyPasswordDTO true "User's old & new passwords."
// @Success 204 {object} boolResponse
// @Failure 400 {object} PasswordValidation
// @Failure 401 {object} boolResponse
// @Failure 500 {object} boolResponse
// @Router /my/password [post]
// @Security Bearer
// @Tags User Page
func (app *appContext) ChangeMyPassword(gc *gin.Context) {
var req ChangeMyPasswordDTO
gc.BindJSON(&req)
if req.Old == "" || req.New == "" {
respondBool(400, false, gc)
}
validation := app.validator.validate(req.New)
for _, val := range validation {
if !val {
app.debug.Printf("%s: Change password failed: Invalid password", gc.GetString("jfId"))
gc.JSON(400, validation)
return
}
}
user, status, err := app.jf.UserByID(gc.GetString("jfId"), false)
if status != 200 || err != nil {
app.err.Printf("Failed to change password: couldn't find user (%d): %+v", status, err)
respondBool(500, false, gc)
return
}
// Authenticate as user to confirm old password.
user, status, err = app.authJf.Authenticate(user.Name, req.Old)
if status != 200 || err != nil {
respondBool(401, false, gc)
return
}
status, err = app.jf.SetPassword(gc.GetString("jfId"), req.Old, req.New)
if (status != 200 && status != 204) || err != nil {
respondBool(500, false, gc)
return
}
if app.config.Section("ombi").Key("enabled").MustBool(false) {
func() {
ombiUser, status, err := app.getOmbiUser(gc.GetString("jfId"))
if status != 200 || err != nil {
app.err.Printf("Failed to get user \"%s\" from ombi (%d): %v", user.Name, status, err)
return
}
ombiUser["password"] = req.New
status, err = app.ombi.ModifyUser(ombiUser)
if status != 200 || err != nil {
app.err.Printf("Failed to set password for ombi user \"%s\" (%d): %v", ombiUser["userName"], status, err)
return
}
app.debug.Printf("Reset password for ombi user \"%s\"", ombiUser["userName"])
}()
}
cookie, err := gc.Cookie("user-refresh")
if err == nil {
app.invalidTokens = append(app.invalidTokens, cookie)
gc.SetCookie("refresh", "invalid", -1, "/my", gc.Request.URL.Hostname(), true, true)
} else {
app.debug.Printf("Couldn't get cookies: %s", err)
}
respondBool(204, true, gc)
}

1131
api-users.go Normal file

File diff suppressed because it is too large Load Diff

1322
api.go

File diff suppressed because it is too large Load Diff

159
args.go Normal file
View File

@@ -0,0 +1,159 @@
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
)
func (app *appContext) loadArgs(firstCall bool) {
if firstCall {
flag.Usage = helpFunc
help := flag.Bool("help", false, "prints this message.")
flag.BoolVar(help, "h", false, "SHORTHAND")
DATA = flag.String("data", app.dataPath, "alternate path to data directory.")
flag.StringVar(DATA, "d", app.dataPath, "SHORTHAND")
CONFIG = flag.String("config", app.configPath, "alternate path to config file.")
flag.StringVar(CONFIG, "c", app.configPath, "SHORTHAND")
HOST = flag.String("host", "", "alternate address to host web ui on.")
PORT = flag.Int("port", 0, "alternate port to host web ui on.")
flag.IntVar(PORT, "p", 0, "SHORTHAND")
DEBUG = flag.Bool("debug", false, "Enables debug logging.")
PPROF = flag.Bool("pprof", false, "Exposes pprof profiler on /debug/pprof.")
SWAGGER = flag.Bool("swagger", false, "Enable swagger at /swagger/index.html")
flag.Parse()
if *help {
flag.Usage()
os.Exit(0)
}
if *SWAGGER {
os.Setenv("SWAGGER", "1")
}
if *DEBUG {
os.Setenv("DEBUG", "1")
}
if *PPROF {
os.Setenv("PPROF", "1")
}
}
if os.Getenv("SWAGGER") == "1" {
*SWAGGER = true
}
if os.Getenv("DEBUG") == "1" {
*DEBUG = true
}
if os.Getenv("PPROF") == "1" {
*PPROF = true
}
// attempt to apply command line flags correctly
if app.configPath == *CONFIG && app.dataPath != *DATA {
app.dataPath = *DATA
app.configPath = filepath.Join(app.dataPath, "config.ini")
} else if app.configPath != *CONFIG && app.dataPath == *DATA {
app.configPath = *CONFIG
} else {
app.configPath = *CONFIG
app.dataPath = *DATA
}
// Previously used for self-restarts but leaving them here as they might be useful.
if v := os.Getenv("JFA_CONFIGPATH"); v != "" {
app.configPath = v
}
if v := os.Getenv("JFA_DATAPATH"); v != "" {
app.dataPath = v
}
os.Setenv("JFA_CONFIGPATH", app.configPath)
os.Setenv("JFA_DATAPATH", app.dataPath)
}
/*
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(stderr, `Usage of jfa-go:
start
start jfa-go as a daemon and run in the background.
stop
stop a daemonized instance of jfa-go.
systemd
generate a systemd .service file.
`)
shortHands := []string{"-help", "-data", "-config", "-port"}
var b bytes.Buffer
// Write defaults into buffer then remove any shorthands
flag.CommandLine.SetOutput(&b)
flag.PrintDefaults()
flag.CommandLine.SetOutput(stderr)
scanner := bufio.NewScanner(&b)
out := ""
line := scanner.Text()
eof := !scanner.Scan()
lastLine := false
for !eof || lastLine {
nextline := scanner.Text()
start := 0
if len(nextline) != 0 {
for nextline[start] == ' ' && start < len(nextline) {
start++
}
}
if strings.Contains(line, "SHORTHAND") || (len(nextline) != 0 && strings.Contains(nextline, "SHORTHAND") && nextline[start] != '-') {
line = nextline
if lastLine {
break
}
eof := !scanner.Scan()
if eof {
lastLine = true
}
continue
}
// if !strings.Contains(line, "SHORTHAND") && !(strings.Contains(nextline, "SHORTHAND") && !strings.Contains(nextline, "-")) {
match := false
for i, c := range line {
if c != '-' {
continue
}
for _, s := range shortHands {
if i+len(s) <= len(line) && line[i:i+len(s)] == s {
out += line[:i+len(s)] + ", " + s[:2] + line[i+len(s):] + "\n"
match = true
break
}
}
}
if !match {
out += line + "\n"
}
line = nextline
if lastLine {
break
}
eof := !scanner.Scan()
if eof {
lastLine = true
}
}
fmt.Fprint(stderr, out)
}

182
auth.go
View File

@@ -4,36 +4,41 @@ import (
"encoding/base64"
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt"
"github.com/hrfee/mediabrowser"
"github.com/lithammer/shortuuid/v3"
)
const (
TOKEN_VALIDITY_SEC = 20 * 60
REFRESH_TOKEN_VALIDITY_SEC = 3600 * 24
)
func (app *appContext) webAuth() gin.HandlerFunc {
return app.authenticate
}
// CreateToken returns a web token as well as a refresh token, which can be used to obtain new tokens.
func CreateToken(userId, jfId string) (string, string, error) {
func CreateToken(userId, jfId string, admin bool) (string, string, error) {
var token, refresh string
claims := jwt.MapClaims{
"valid": true,
"id": userId,
"exp": strconv.FormatInt(time.Now().Add(time.Minute*20).Unix(), 10),
"exp": time.Now().Add(time.Second * TOKEN_VALIDITY_SEC).Unix(),
"jfid": jfId,
"admin": admin,
"type": "bearer",
}
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
token, err := tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
if err != nil {
return "", "", err
}
claims["exp"] = strconv.FormatInt(time.Now().Add(time.Hour*24).Unix(), 10)
claims["exp"] = time.Now().Add(time.Second * REFRESH_TOKEN_VALIDITY_SEC).Unix()
claims["type"] = "refresh"
tk = jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
refresh, err = tk.SignedString([]byte(os.Getenv("JFA_SECRET")))
@@ -43,8 +48,9 @@ func CreateToken(userId, jfId string) (string, string, error) {
return token, refresh, nil
}
// Check header for token
func (app *appContext) authenticate(gc *gin.Context) {
// Caller should return if this returns false.
func (app *appContext) decodeValidateAuthHeader(gc *gin.Context) (claims jwt.MapClaims, ok bool) {
ok = false
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
if header[0] != "Bearer" {
app.debug.Println("Invalid authorization header")
@@ -57,23 +63,48 @@ func (app *appContext) authenticate(gc *gin.Context) {
respond(401, "Unauthorized", gc)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
expiryUnix, err := strconv.ParseInt(claims["exp"].(string), 10, 64)
claims, ok = token.Claims.(jwt.MapClaims)
if !ok {
app.debug.Println("Invalid JWT")
respond(401, "Unauthorized", gc)
return
}
expiryUnix := int64(claims["exp"].(float64))
if err != nil {
app.debug.Printf("Auth denied: %s", err)
respond(401, "Unauthorized", gc)
ok = false
return
}
expiry := time.Unix(expiryUnix, 0)
if !(ok && token.Valid && claims["type"].(string) == "bearer" && expiry.After(time.Now())) {
app.debug.Printf("Auth denied: Invalid token")
// app.debug.Printf("Expiry: %+v, OK: %t, Valid: %t, ClaimType: %s\n", expiry, ok, token.Valid, claims["type"].(string))
respond(401, "Unauthorized", gc)
ok = false
return
}
ok = true
return
}
// Check header for token
func (app *appContext) authenticate(gc *gin.Context) {
claims, ok := app.decodeValidateAuthHeader(gc)
if !ok {
return
}
isAdminToken := claims["admin"].(bool)
if !isAdminToken {
app.debug.Printf("Auth denied: Token was not for admin access")
respond(401, "Unauthorized", gc)
return
}
userID := claims["id"].(string)
jfID := claims["jfid"].(string)
match := false
for _, user := range app.users {
for _, user := range app.adminUsers {
if user.UserID == userID {
match = true
break
@@ -86,6 +117,7 @@ func (app *appContext) authenticate(gc *gin.Context) {
}
gc.Set("jfId", jfID)
gc.Set("userId", userID)
gc.Set("userMode", false)
app.debug.Println("Auth succeeded")
gc.Next()
}
@@ -101,8 +133,46 @@ type getTokenDTO struct {
Token string `json:"token" example:"kjsdklsfdkljfsjsdfklsdfkldsfjdfskjsdfjklsdf"` // API token for use with everything else.
}
func (app *appContext) decodeValidateLoginHeader(gc *gin.Context) (username, password string, ok bool) {
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
auth, _ := base64.StdEncoding.DecodeString(header[1])
creds := strings.SplitN(string(auth), ":", 2)
username = creds[0]
password = creds[1]
ok = false
if username == "" || password == "" {
app.debug.Println("Auth denied: blank username/password")
respond(401, "Unauthorized", gc)
return
}
ok = true
return
}
func (app *appContext) validateJellyfinCredentials(username, password string, gc *gin.Context) (user mediabrowser.User, ok bool) {
ok = false
user, status, err := app.authJf.Authenticate(username, password)
if status != 200 || err != nil {
if status == 401 || status == 400 {
app.info.Println("Auth denied: Invalid username/password (Jellyfin)")
respond(401, "Unauthorized", gc)
return
}
if status == 403 {
app.info.Println("Auth denied: Jellyfin account disabled")
respond(403, "yourAccountWasDisabled", gc)
return
}
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin (%d/%s)", status, err)
respond(500, "Jellyfin error", gc)
return
}
ok = true
return
}
// @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
@@ -111,18 +181,14 @@ type getTokenDTO struct {
// @Security getTokenAuth
func (app *appContext) getTokenLogin(gc *gin.Context) {
app.info.Println("Token requested (login attempt)")
header := strings.SplitN(gc.Request.Header.Get("Authorization"), " ", 2)
auth, _ := base64.StdEncoding.DecodeString(header[1])
creds := strings.SplitN(string(auth), ":", 2)
var userID, jfID string
if creds[0] == "" || creds[1] == "" {
app.debug.Println("Auth denied: blank username/password")
respond(401, "Unauthorized", gc)
username, password, ok := app.decodeValidateLoginHeader(gc)
if !ok {
return
}
var userID, jfID string
match := false
for _, user := range app.users {
if user.Username == creds[0] && user.Password == creds[1] {
for _, user := range app.adminUsers {
if user.Username == username && user.Password == password {
match = true
app.debug.Println("Found existing user")
userID = user.UserID
@@ -135,24 +201,20 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
return
}
if !match {
var status int
var err error
var user map[string]interface{}
user, status, err = app.authJf.Authenticate(creds[0], creds[1])
if status != 200 || err != nil {
if status == 401 || status == 400 {
app.info.Println("Auth denied: Invalid username/password (Jellyfin)")
respond(401, "Unauthorized", gc)
return
}
app.err.Printf("Auth failed: Couldn't authenticate with Jellyfin (%d/%s)", status, err)
respond(500, "Jellyfin error", gc)
user, ok := app.validateJellyfinCredentials(username, password, gc)
if !ok {
return
}
jfID = user["Id"].(string)
if app.config.Section("ui").Key("admin_only").MustBool(true) {
if !user["Policy"].(map[string]interface{})["IsAdministrator"].(bool) {
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", creds[0])
jfID = user.ID
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.GetEmailsKey(jfID); ok {
accountsAdmin = emailStore.Admin
}
accountsAdmin = accountsAdmin || (adminOnly && user.Policy.IsAdministrator)
if !accountsAdmin {
app.debug.Printf("Auth denied: Users \"%s\" isn't admin", username)
respond(401, "Unauthorized", gc)
return
}
@@ -162,10 +224,10 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
newUser := User{
UserID: userID,
}
app.debug.Printf("Token generated for user \"%s\"", creds[0])
app.users = append(app.users, newUser)
app.debug.Printf("Token generated for user \"%s\"", username)
app.adminUsers = append(app.adminUsers, newUser)
}
token, refresh, err := CreateToken(userID, jfID)
token, refresh, err := CreateToken(userID, jfID, true)
if err != nil {
app.err.Printf("getToken failed: Couldn't generate token (%s)", err)
respond(500, "Couldn't generate token", gc)
@@ -175,15 +237,9 @@ func (app *appContext) getTokenLogin(gc *gin.Context) {
gc.JSON(200, getTokenDTO{token})
}
// @Summary Grabs an API token using a refresh token from cookies.
// @Produce json
// @Success 200 {object} getTokenDTO
// @Failure 401 {object} stringResponse
// @Router /token/refresh [get]
// @tags Auth
func (app *appContext) getTokenRefresh(gc *gin.Context) {
app.debug.Println("Token requested (refresh token)")
cookie, err := gc.Cookie("refresh")
func (app *appContext) decodeValidateRefreshCookie(gc *gin.Context, cookieName string) (claims jwt.MapClaims, ok bool) {
ok = false
cookie, err := gc.Cookie(cookieName)
if err != nil || cookie == "" {
app.debug.Printf("getTokenRefresh denied: Couldn't get token: %s", err)
respond(400, "Couldn't get token", gc)
@@ -202,27 +258,45 @@ func (app *appContext) getTokenRefresh(gc *gin.Context) {
respond(400, "Invalid token", gc)
return
}
claims, ok := token.Claims.(jwt.MapClaims)
expiryUnix, err := strconv.ParseInt(claims["exp"].(string), 10, 64)
claims, ok = token.Claims.(jwt.MapClaims)
expiryUnix := int64(claims["exp"].(float64))
if err != nil {
app.debug.Printf("getTokenRefresh: Invalid token expiry: %s", err)
respond(401, "Invalid token", gc)
ok = false
return
}
expiry := time.Unix(expiryUnix, 0)
if !(ok && token.Valid && claims["type"].(string) == "refresh" && expiry.After(time.Now())) {
app.debug.Printf("getTokenRefresh: Invalid token: %s", err)
app.debug.Printf("getTokenRefresh: Invalid token: %+v", err)
respond(401, "Invalid token", gc)
ok = false
return
}
ok = true
return
}
// @Summary Grabs an API token using a refresh token from cookies.
// @Produce json
// @Success 200 {object} getTokenDTO
// @Failure 401 {object} stringResponse
// @Router /token/refresh [get]
// @tags Auth
func (app *appContext) getTokenRefresh(gc *gin.Context) {
app.debug.Println("Token requested (refresh token)")
claims, ok := app.decodeValidateRefreshCookie(gc, "refresh")
if !ok {
return
}
userID := claims["id"].(string)
jfID := claims["jfid"].(string)
jwt, refresh, err := CreateToken(userID, jfID)
jwt, refresh, err := CreateToken(userID, jfID, true)
if err != nil {
app.err.Printf("getTokenRefresh failed: Couldn't generate token (%s)", err)
respond(500, "Couldn't generate token", gc)
return
}
gc.SetCookie("refresh", refresh, (3600 * 24), "/", gc.Request.URL.Hostname(), true, true)
gc.SetCookie("refresh", refresh, REFRESH_TOKEN_VALIDITY_SEC, "/", gc.Request.URL.Hostname(), true, true)
gc.JSON(200, getTokenDTO{jwt})
}

69
autostart.go Normal file
View File

@@ -0,0 +1,69 @@
// +build tray
package main
import (
"log"
"os"
"path/filepath"
"github.com/emersion/go-autostart"
"github.com/getlantern/systray"
)
type Autostart struct {
as *autostart.App
enabled bool
menuitem *systray.MenuItem
clicked chan bool
}
func NewAutostart(name, displayname, trayName, trayTooltip string) *Autostart {
a := &Autostart{
as: &autostart.App{
Name: name,
DisplayName: displayname,
},
enabled: true,
clicked: make(chan bool),
}
a.menuitem = systray.AddMenuItemCheckbox(trayName, trayTooltip, a.as.IsEnabled())
command := os.Args
command[0], _ = filepath.Abs(command[0])
// Make sure to replace any relative paths with absolute ones
pathArgs := []string{"-d", "-data", "-c", "-config"}
for i := 1; i < len(command); i++ {
isPath := false
for _, p := range pathArgs {
if command[i-1] == p {
isPath = true
break
}
}
if isPath {
command[i], _ = filepath.Abs(command[i])
}
}
a.as.Exec = command
return a
}
func (a *Autostart) HandleCheck() {
for range a.menuitem.ClickedCh {
if !a.menuitem.Checked() {
if err := a.as.Enable(); err != nil {
log.Printf("Failed to enable autostart on login: %v", err)
} else {
a.menuitem.Check()
log.Printf("Enabled autostart")
}
} else {
if err := a.as.Disable(); err != nil {
log.Printf("Failed to disable autostart on login: %v", err)
} else {
a.menuitem.Uncheck()
log.Printf("Disabled autostart")
}
}
}
}

View File

@@ -5,14 +5,14 @@ import (
"log"
)
// TimeoutHandler recovers from an http timeout.
// TimeoutHandler recovers from an http timeout or panic.
type TimeoutHandler func()
// NewTimeoutHandler returns a new Timeout handler.
func NewTimeoutHandler(name, addr string, noFail bool) TimeoutHandler {
return func() {
if r := recover(); r != nil {
out := fmt.Sprintf("Failed to authenticate with %s @ %s: Timed out", name, addr)
out := fmt.Sprintf("Failed to authenticate with %s @ \"%s\": Timed out", name, addr)
if noFail {
log.Print(out)
} else {

150
config.go
View File

@@ -2,6 +2,8 @@ package main
import (
"fmt"
"io/fs"
"os"
"path/filepath"
"strconv"
"strings"
@@ -9,6 +11,25 @@ import (
"gopkg.in/ini.v1"
)
var emailEnabled = false
var messagesEnabled = false
var telegramEnabled = false
var discordEnabled = false
var matrixEnabled = false
func (app *appContext) GetPath(sect, key string) (fs.FS, string) {
val := app.config.Section(sect).Key(key).MustString("")
if strings.HasPrefix(val, "jfa-go:") {
return localFS, strings.TrimPrefix(val, "jfa-go:")
}
dir, file := filepath.Split(val)
return os.DirFS(dir), file
}
func (app *appContext) MustSetValue(section, key, val string) {
app.config.Section(section).Key(key).SetValue(app.config.Section(section).Key(key).MustString(val))
}
func (app *appContext) loadConfig() error {
var err error
app.config, err = ini.Load(app.configPath)
@@ -16,50 +37,145 @@ func (app *appContext) loadConfig() error {
return err
}
app.config.Section("jellyfin").Key("public_server").SetValue(app.config.Section("jellyfin").Key("public_server").MustString(app.config.Section("jellyfin").Key("server").String()))
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 key.Name() != "html_templates" {
if name := key.Name(); name != "html_templates" && name != "lang_files" {
key.SetValue(key.MustString(filepath.Join(app.dataPath, (key.Name() + ".json"))))
}
}
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template"} {
for _, key := range []string{"user_configuration", "user_displayprefs", "user_profiles", "ombi_template", "invites", "emails", "user_template", "custom_emails", "users", "telegram_users", "discord_users", "matrix_users", "announcements", "custom_user_page_content"} {
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".json"))))
}
for _, key := range []string{"matrix_sql"} {
app.config.Section("files").Key(key).SetValue(app.config.Section("files").Key(key).MustString(filepath.Join(app.dataPath, (key + ".db"))))
}
app.URLBase = strings.TrimSuffix(app.config.Section("ui").Key("url_base").MustString(""), "/")
app.config.Section("email").Key("no_username").SetValue(strconv.FormatBool(app.config.Section("email").Key("no_username").MustBool(false)))
app.config.Section("password_resets").Key("email_html").SetValue(app.config.Section("password_resets").Key("email_html").MustString(filepath.Join(app.localPath, "email.html")))
app.config.Section("password_resets").Key("email_text").SetValue(app.config.Section("password_resets").Key("email_text").MustString(filepath.Join(app.localPath, "email.txt")))
app.MustSetValue("password_resets", "email_html", "jfa-go:"+"email.html")
app.MustSetValue("password_resets", "email_text", "jfa-go:"+"email.txt")
app.config.Section("invite_emails").Key("email_html").SetValue(app.config.Section("invite_emails").Key("email_html").MustString(filepath.Join(app.localPath, "invite-email.html")))
app.config.Section("invite_emails").Key("email_text").SetValue(app.config.Section("invite_emails").Key("email_text").MustString(filepath.Join(app.localPath, "invite-email.txt")))
app.MustSetValue("invite_emails", "email_html", "jfa-go:"+"invite-email.html")
app.MustSetValue("invite_emails", "email_text", "jfa-go:"+"invite-email.txt")
app.config.Section("notifications").Key("expiry_html").SetValue(app.config.Section("notifications").Key("expiry_html").MustString(filepath.Join(app.localPath, "expired.html")))
app.config.Section("notifications").Key("expiry_text").SetValue(app.config.Section("notifications").Key("expiry_text").MustString(filepath.Join(app.localPath, "expired.txt")))
app.MustSetValue("email_confirmation", "email_html", "jfa-go:"+"confirmation.html")
app.MustSetValue("email_confirmation", "email_text", "jfa-go:"+"confirmation.txt")
app.config.Section("notifications").Key("created_html").SetValue(app.config.Section("notifications").Key("created_html").MustString(filepath.Join(app.localPath, "created.html")))
app.config.Section("notifications").Key("created_text").SetValue(app.config.Section("notifications").Key("created_text").MustString(filepath.Join(app.localPath, "created.txt")))
app.MustSetValue("notifications", "expiry_html", "jfa-go:"+"expired.html")
app.MustSetValue("notifications", "expiry_text", "jfa-go:"+"expired.txt")
app.config.Section("deletion").Key("email_html").SetValue(app.config.Section("deletion").Key("email_html").MustString(filepath.Join(app.localPath, "deleted.html")))
app.config.Section("deletion").Key("email_text").SetValue(app.config.Section("deletion").Key("email_text").MustString(filepath.Join(app.localPath, "deleted.txt")))
app.MustSetValue("notifications", "created_html", "jfa-go:"+"created.html")
app.MustSetValue("notifications", "created_text", "jfa-go:"+"created.txt")
app.config.Section("jellyfin").Key("version").SetValue(VERSION)
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)
}
// Deletion template is good enough for these as well.
app.MustSetValue("disable_enable", "disabled_html", "jfa-go:"+"deleted.html")
app.MustSetValue("disable_enable", "disabled_text", "jfa-go:"+"deleted.txt")
app.MustSetValue("disable_enable", "enabled_html", "jfa-go:"+"deleted.html")
app.MustSetValue("disable_enable", "enabled_text", "jfa-go:"+"deleted.txt")
app.MustSetValue("welcome_email", "email_html", "jfa-go:"+"welcome.html")
app.MustSetValue("welcome_email", "email_text", "jfa-go:"+"welcome.txt")
app.MustSetValue("template_email", "email_html", "jfa-go:"+"template.html")
app.MustSetValue("template_email", "email_text", "jfa-go:"+"template.txt")
app.MustSetValue("user_expiry", "behaviour", "disable_user")
app.MustSetValue("user_expiry", "email_html", "jfa-go:"+"user-expired.html")
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")
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", VERSION, COMMIT))
app.config.Section("jellyfin").Key("device_id").SetValue(fmt.Sprintf("jfa-go-%s-%s", version, commit))
// These two settings are pretty much the same
url1 := app.config.Section("invite_emails").Key("url_base").String()
url2 := app.config.Section("password_resets").Key("url_base").String()
app.MustSetValue("password_resets", "url_base", strings.TrimSuffix(url1, "/invite"))
app.MustSetValue("invite_emails", "url_base", url2)
messagesEnabled = app.config.Section("messages").Key("enabled").MustBool(false)
telegramEnabled = app.config.Section("telegram").Key("enabled").MustBool(false)
discordEnabled = app.config.Section("discord").Key("enabled").MustBool(false)
matrixEnabled = app.config.Section("matrix").Key("enabled").MustBool(false)
if !messagesEnabled {
emailEnabled = false
telegramEnabled = false
discordEnabled = false
matrixEnabled = false
} else if app.config.Section("email").Key("method").MustString("") == "" {
emailEnabled = false
} else {
emailEnabled = true
}
if !emailEnabled && !telegramEnabled && !discordEnabled && !matrixEnabled {
messagesEnabled = false
}
app.MustSetValue("updates", "enabled", "true")
releaseChannel := app.config.Section("updates").Key("channel").String()
if app.config.Section("updates").Key("enabled").MustBool(false) {
v := version
if releaseChannel == "stable" {
if version == "git" {
v = "0.0.0"
}
} else if releaseChannel == "unstable" {
v = "git"
}
app.updater = newUpdater(baseURL, namespace, repo, v, commit, updater)
}
if releaseChannel == "" {
if version == "git" {
releaseChannel = "unstable"
} else {
releaseChannel = "stable"
}
app.MustSetValue("updates", "channel", releaseChannel)
}
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
app.storage.lang.chosenUserLang = oldFormLang
}
newFormLang := app.config.Section("ui").Key("language-form").MustString("")
if newFormLang != "" {
app.storage.lang.chosenFormLang = newFormLang
app.storage.lang.chosenUserLang = newFormLang
}
app.storage.lang.chosenAdminLang = app.config.Section("ui").Key("language-admin").MustString("en-us")
app.storage.lang.chosenEmailLang = app.config.Section("email").Key("language").MustString("en-us")
app.storage.lang.chosenPWRLang = app.config.Section("password_resets").Key("language").MustString("en-us")
app.storage.lang.chosenTelegramLang = app.config.Section("telegram").Key("language").MustString("en-us")
app.email = NewEmailer(app)

View File

@@ -1,11 +1,4 @@
### fixconfig
Python's `json` library retains the order of data in a JSON file, which meant settings sent to the web page would be in the right order. Go's `encoding/json` and maps do not retain order, so this script opens the json file, and for each section, adds an "order" list which tells the web page in which order to display settings.
Python's `json` library retains the order of data in a JSON file, which meant settings sent to the web page would be in the right order. Go's `encoding/json` and maps do not retain order, so `enumerate/enumerate_config.py` opens the json file, and for each section, adds an "order" array which tells the web page in which order to display settings.
Specify the input and output files with `-i` and `-o` respectively.
### jsontostruct
Generates a go struct from `config-base.json`. I wrote this because i was annoyed with the `ini` library, but i've since realised mapping the ini values onto it is painful.

File diff suppressed because it is too large Load Diff

View File

@@ -1,541 +0,0 @@
package main
type Metadata struct{
Name string `json:"name"`
Description string `json:"description"`
}
type Config struct{
Order []string `json:"order"`
Jellyfin struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Username struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"username"`
} `json:"username" cfg:"username"`
Password struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"password"`
} `json:"password" cfg:"password"`
Server struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"server"`
} `json:"server" cfg:"server"`
PublicServer struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"public_server"`
} `json:"public_server" cfg:"public_server"`
Client struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"client"`
} `json:"client" cfg:"client"`
Version struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"version"`
} `json:"version" cfg:"version"`
Device struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"device"`
} `json:"device" cfg:"device"`
DeviceId struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"device_id"`
} `json:"device_id" cfg:"device_id"`
} `json:"jellyfin"`
Ui struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Theme struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Options []string `json:"options"`
Value string `json:"value" cfg:"theme"`
} `json:"theme" cfg:"theme"`
Host struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"host"`
} `json:"host" cfg:"host"`
Port struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value int `json:"value" cfg:"port"`
} `json:"port" cfg:"port"`
JellyfinLogin struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"jellyfin_login"`
} `json:"jellyfin_login" cfg:"jellyfin_login"`
AdminOnly struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"admin_only"`
} `json:"admin_only" cfg:"admin_only"`
Username struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"username"`
} `json:"username" cfg:"username"`
Password struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"password"`
} `json:"password" cfg:"password"`
Email struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"email"`
} `json:"email" cfg:"email"`
Debug struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"debug"`
} `json:"debug" cfg:"debug"`
ContactMessage struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"contact_message"`
} `json:"contact_message" cfg:"contact_message"`
HelpMessage struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"help_message"`
} `json:"help_message" cfg:"help_message"`
SuccessMessage struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"success_message"`
} `json:"success_message" cfg:"success_message"`
Bs5 struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"bs5"`
} `json:"bs5" cfg:"bs5"`
} `json:"ui"`
PasswordValidation struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Enabled struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"enabled"`
} `json:"enabled" cfg:"enabled"`
MinLength struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"min_length"`
} `json:"min_length" cfg:"min_length"`
Upper struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"upper"`
} `json:"upper" cfg:"upper"`
Lower struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"lower"`
} `json:"lower" cfg:"lower"`
Number struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"number"`
} `json:"number" cfg:"number"`
Special struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"special"`
} `json:"special" cfg:"special"`
} `json:"password_validation"`
Email struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
NoUsername struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"no_username"`
} `json:"no_username" cfg:"no_username"`
Use24H struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"use_24h"`
} `json:"use_24h" cfg:"use_24h"`
DateFormat struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"date_format"`
} `json:"date_format" cfg:"date_format"`
Message struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"message"`
} `json:"message" cfg:"message"`
Method struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Options []string `json:"options"`
Value string `json:"value" cfg:"method"`
} `json:"method" cfg:"method"`
Address struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"address"`
} `json:"address" cfg:"address"`
From struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"from"`
} `json:"from" cfg:"from"`
} `json:"email"`
PasswordResets struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Enabled struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"enabled"`
} `json:"enabled" cfg:"enabled"`
WatchDirectory struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"watch_directory"`
} `json:"watch_directory" cfg:"watch_directory"`
EmailHtml struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"email_html"`
} `json:"email_html" cfg:"email_html"`
EmailText struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"email_text"`
} `json:"email_text" cfg:"email_text"`
Subject struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"subject"`
} `json:"subject" cfg:"subject"`
} `json:"password_resets"`
InviteEmails struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Enabled struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"enabled"`
} `json:"enabled" cfg:"enabled"`
EmailHtml struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"email_html"`
} `json:"email_html" cfg:"email_html"`
EmailText struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"email_text"`
} `json:"email_text" cfg:"email_text"`
Subject struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"subject"`
} `json:"subject" cfg:"subject"`
UrlBase struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"url_base"`
} `json:"url_base" cfg:"url_base"`
} `json:"invite_emails"`
Notifications struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Enabled struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value bool `json:"value" cfg:"enabled"`
} `json:"enabled" cfg:"enabled"`
ExpiryHtml struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"expiry_html"`
} `json:"expiry_html" cfg:"expiry_html"`
ExpiryText struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"expiry_text"`
} `json:"expiry_text" cfg:"expiry_text"`
CreatedHtml struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"created_html"`
} `json:"created_html" cfg:"created_html"`
CreatedText struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"created_text"`
} `json:"created_text" cfg:"created_text"`
} `json:"notifications"`
Mailgun struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
ApiUrl struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"api_url"`
} `json:"api_url" cfg:"api_url"`
ApiKey struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"api_key"`
} `json:"api_key" cfg:"api_key"`
} `json:"mailgun"`
Smtp struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Encryption struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Options []string `json:"options"`
Value string `json:"value" cfg:"encryption"`
} `json:"encryption" cfg:"encryption"`
Server struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"server"`
} `json:"server" cfg:"server"`
Port struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value int `json:"value" cfg:"port"`
} `json:"port" cfg:"port"`
Password struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"password"`
} `json:"password" cfg:"password"`
} `json:"smtp"`
Files struct{
Order []string `json:"order"`
Meta Metadata `json:"meta"`
Invites struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"invites"`
} `json:"invites" cfg:"invites"`
Emails struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"emails"`
} `json:"emails" cfg:"emails"`
UserTemplate struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"user_template"`
} `json:"user_template" cfg:"user_template"`
UserConfiguration struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"user_configuration"`
} `json:"user_configuration" cfg:"user_configuration"`
UserDisplayprefs struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"user_displayprefs"`
} `json:"user_displayprefs" cfg:"user_displayprefs"`
CustomCss struct{
Name string `json:"name"`
Required bool `json:"required"`
Restart bool `json:"requires_restart"`
Description string `json:"description"`
Type string `json:"type"`
Value string `json:"value" cfg:"custom_css"`
} `json:"custom_css" cfg:"custom_css"`
} `json:"files"`
}

View File

@@ -1,25 +1,64 @@
@import "a17t.css";
@import "remixicon.css";
@import "modal.css";
@import "dark.css";
@import "tooltip.css";
@import "loader.css";
@import "./modal.css";
@import "./dark.css";
@import "./tooltip.css";
@import "./loader.css";
@import "./fonts.css";
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--border-width-default: 2px;
--border-width-2: 3px;
--border-width-4: 5px;
--border-width-8: 8px;
font-family: 'Hanken Grotesk', ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.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,63 +66,47 @@
@media (max-width: 1100px) {
.page-container {
margin: 2%;
margin-top: 5rem;
}
}
@media screen and (max-width: 750px) {
@media screen and (max-width: 1000px) {
:root {
font-size: 0.9rem;
}
.table-responsive table {
min-width: 660px;
min-width: 800px;
}
}
.chip.btn:hover:not([disabled]):not(.textarea),
.chip.btn:focus:not([disabled]):not(.textarea) {
filter: brightness(var(--button-filter-brightness,95%));
}
.banner {
margin: calc(-1 * var(--spacing-4,1rem));
}
.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 {
margin-top: var(--spacing-4,1rem);
padding: var(--spacing-4,1rem);
}
div.card:contains(section.banner.footer) {
padding-bottom: 0px;
}
.tab-button {
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-1 {
margin-right: 1rem;
}
.pb-1 {
padding-bottom: 1rem;
}
.pl-1 {
padding-left: 1rem;
}
.al {
text-align: left;
}
@@ -92,6 +115,18 @@
text-align: right;
}
.ac {
text-align: center;
}
.w-100 {
width: 100%;
}
.h-100 {
height: 100%;
}
.inline-block {
display: inline-block;
}
@@ -100,20 +135,11 @@
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;
@@ -133,6 +159,33 @@
margin: 0.5rem;
}
p.sm,
span.sm:not(.heading) {
font-size: 0.75rem;
}
.col.sm {
margin: .25rem;
}
.flex-col {
display: flex;
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;
@@ -142,14 +195,6 @@
}
}
.fr {
float: right;
}
.monospace {
background-color: inherit; /* so we can use a17t code blocks */
}
sup.\~critical, .text-critical {
color: var(--color-critical-normal-content);
}
@@ -200,7 +245,7 @@ sup.\~critical, .text-critical {
max-width: 40%;
min-width: 10rem;
display: flex;
justify-content: center;
justify-content: start;
align-items: center;
}
@@ -252,15 +297,23 @@ sup.\~critical, .text-critical {
width: 100%;
}
.flex-auto {
flex: auto;
}
.center {
justify-content: center;
}
.middle {
align-items: center;
}
.no-lp {
padding-left: 0px;
}
.block {
.block {
display: block;
}
@@ -307,10 +360,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 {
@@ -322,8 +373,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;
}
@@ -351,17 +400,28 @@ 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;
}
.table-responsive {
overflow-x: auto;
font-size: 0.9rem;
}
#notification-box {
@@ -375,3 +435,152 @@ p.top {
padding-bottom: 0.5rem;
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 {
height: 0.5rem;
width: 0.5rem;
border-radius: 50%;
}
.circle.\~urge {
background-color: var(--color-urge-200);
}
.markdown-box {
max-height: 20rem;
display: block;
overflow-y: scroll;
}
a:link:not(.lang-link):not(.\~urge) {
color: var(--color-urge-200);
}
a:visited:not(.lang-link):not(.\~urge) {
color: var(--color-urge-100);
}
a:hover:not(.lang-link):not(.\~urge), a:active:not(.lang-link):not(.\~urge) {
color: var(--color-urge-200);
}
a.button,
a.button:link,
a.button:visited,
a.button:focus,
a.buton:hover {
color: var(--color-content) !important;
}
.link-center {
display: block;
text-align: center;
}
/* .search {
max-width: 15rem;
min-width: 10rem;
} */
td.img-circle {
width: 32px;
height: 32px;
}
span.img-circle.lg {
width: 64px;
height: 64px;
}
span.shield.img-circle {
padding: 0.2rem;
}
img.img-circle {
border-radius: 50%;
vertical-align: middle;
}
.table td.sm {
padding-top: 0.1rem;
padding-bottom: 0.1rem;
}
.table-inline {
display: flex !important;
align-items: center;
}
div.card:contains(section.banner.footer) {
padding-bottom: 0px;
}
.card.sectioned {
padding: 0px;
}
.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;
}
.g-recaptcha {
overflow: hidden;
width: 296px;
height: 72px;
transform: scale(1.1);
transform-origin: top left;
}
.g-recaptcha iframe {
margin: -2px 0px 0px -4px;
}

View File

@@ -1,87 +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;
}

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;

44
css/fonts.css Normal file
View File

@@ -0,0 +1,44 @@
/* hanken-grotesk-regular - cyrillic-ext_latin_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Hanken Grotesk';
font-style: normal;
font-weight: 400;
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* hanken-grotesk-500 - cyrillic-ext_latin_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Hanken Grotesk';
font-style: normal;
font-weight: 500;
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* hanken-grotesk-500italic - cyrillic-ext_latin_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Hanken Grotesk';
font-style: italic;
font-weight: 500;
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-500italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ *
}
/* hanken-grotesk-700 - cyrillic-ext_latin_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Hanken Grotesk';
font-style: normal;
font-weight: 700;
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* hanken-grotesk-700italic - cyrillic-ext_latin_vietnamese */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Hanken Grotesk';
font-style: italic;
font-weight: 700;
src: url('../fonts/hanken-grotesk-v8-cyrillic-ext_latin_vietnamese-700italic.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}

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;

101
daemon.go
View File

@@ -2,27 +2,110 @@ 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")
emails := app.storage.GetEmails()
for _, email := range emails {
_, status, err := app.jf.UserByID(email.JellyfinID, false)
if status == 200 && err != nil {
continue
}
app.storage.DeleteEmailsKey(email.JellyfinID)
}
}
// clearDiscord does the same as clearEmails, but for Discord Users.
func (app *appContext) clearDiscord() {
app.debug.Println("Housekeeping: removing unused Discord IDs")
discordUsers := app.storage.GetDiscord()
for _, discordUser := range discordUsers {
_, status, err := app.jf.UserByID(discordUser.JellyfinID, false)
if status == 200 && err != nil {
continue
}
app.storage.DeleteDiscordKey(discordUser.JellyfinID)
}
}
// clearMatrix does the same as clearEmails, but for Matrix Users.
func (app *appContext) clearMatrix() {
app.debug.Println("Housekeeping: removing unused Matrix IDs")
matrixUsers := app.storage.GetMatrix()
for _, matrixUser := range matrixUsers {
_, status, err := app.jf.UserByID(matrixUser.JellyfinID, false)
if status == 200 && err != nil {
continue
}
app.storage.DeleteMatrixKey(matrixUser.JellyfinID)
}
}
// clearTelegram does the same as clearEmails, but for Telegram Users.
func (app *appContext) clearTelegram() {
app.debug.Println("Housekeeping: removing unused Telegram IDs")
telegramUsers := app.storage.GetTelegram()
for _, telegramUser := range telegramUsers {
_, status, err := app.jf.UserByID(telegramUser.JellyfinID, false)
if status == 200 && err != nil {
continue
}
app.storage.DeleteTelegramKey(telegramUser.JellyfinID)
}
}
// https://bbengfort.github.io/snippets/2016/06/26/background-work-goroutines-timer.html THANKS
type repeater struct {
type housekeepingDaemon struct {
Stopped bool
ShutdownChannel chan string
Interval time.Duration
period time.Duration
jobs []func(app *appContext)
app *appContext
}
func newRepeater(interval time.Duration, app *appContext) *repeater {
return &repeater{
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 *repeater) run() {
func (rt *housekeepingDaemon) run() {
rt.app.info.Println("Invite daemon started")
for {
select {
@@ -33,16 +116,18 @@ func (rt *repeater) run() {
break
}
started := time.Now()
rt.app.storage.loadInvites()
rt.app.debug.Println("Daemon: Checking invites")
rt.app.checkInvites()
for _, job := range rt.jobs {
job(rt.app)
}
finished := time.Now()
duration := finished.Sub(started)
rt.period = rt.Interval - duration
}
}
func (rt *repeater) shutdown() {
func (rt *housekeepingDaemon) Shutdown() {
rt.Stopped = true
rt.ShutdownChannel <- "Down"
<-rt.ShutdownChannel

720
discord.go Normal file
View File

@@ -0,0 +1,720 @@
package main
import (
"fmt"
"strings"
"time"
dg "github.com/bwmarrin/discordgo"
"github.com/timshannon/badgerhold/v4"
)
type DiscordDaemon struct {
Stopped bool
ShutdownChannel chan string
bot *dg.Session
username string
tokens map[string]VerifToken // Map of pins to tokens.
verifiedTokens map[string]DiscordUser // Map of token pins to discord users.
channelID, channelName, inviteChannelID, inviteChannelName string
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) {
token := app.config.Section("discord").Key("token").String()
if token == "" {
return nil, fmt.Errorf("token was blank")
}
bot, err := dg.New("Bot " + token)
if err != nil {
return nil, err
}
dd := &DiscordDaemon{
Stopped: false,
ShutdownChannel: make(chan string),
bot: bot,
tokens: map[string]VerifToken{},
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.GetDiscord() {
dd.users[user.ID] = user
}
return dd, nil
}
// NewAuthToken generates an 8-character pin in the form "A1-2B-CD".
func (d *DiscordDaemon) NewAuthToken() string {
pin := genAuthToken()
d.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: ""}
return pin
}
// NewAssignedAuthToken generates an 8-character pin in the form "A1-2B-CD",
// and assigns it for access only with the given Jellyfin ID.
func (d *DiscordDaemon) NewAssignedAuthToken(id string) string {
pin := genAuthToken()
d.tokens[pin] = VerifToken{Expiry: time.Now().Add(VERIF_TOKEN_EXPIRY_SEC * time.Second), JellyfinID: id}
return pin
}
func (d *DiscordDaemon) NewUnknownUser(channelID, userID, discrim, username string) DiscordUser {
user := DiscordUser{
ChannelID: channelID,
ID: userID,
Username: username,
Discriminator: discrim,
}
return user
}
func (d *DiscordDaemon) MustGetUser(channelID, userID, discrim, username string) DiscordUser {
if user, ok := d.users[userID]; ok {
return user
}
return d.NewUnknownUser(channelID, userID, discrim, username)
}
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)
return
}
// Wait for everything to populate, it's slow sometimes.
for d.bot.State == nil {
continue
}
for d.bot.State.User == nil {
continue
}
d.username = d.bot.State.User.Username
for d.bot.State.Guilds == nil {
continue
}
// Choose the last guild (server), for now we don't really support multiple anyway
d.guildID = d.bot.State.Guilds[len(d.bot.State.Guilds)-1].ID
guild, err := d.bot.Guild(d.guildID)
if err != nil {
d.app.err.Printf("Discord: Failed to get guild: %v", err)
}
d.serverChannelName = guild.Name
d.serverName = guild.Name
if channel := d.app.config.Section("discord").Key("channel").String(); channel != "" {
d.channelName = channel
d.serverChannelName += "/" + channel
}
if d.app.config.Section("discord").Key("provide_invite").MustBool(false) {
if invChannel := d.app.config.Section("discord").Key("invite_channel").String(); invChannel != "" {
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
var err error
if d.inviteChannelName == "" {
d.app.err.Println("Discord: Cannot create invite without channel specified in settings.")
return
}
if d.inviteChannelID == "" {
channels, err := d.bot.GuildChannels(d.guildID)
if err != nil {
d.app.err.Printf("Discord: Couldn't get channel list: %v", err)
return
}
found := false
for _, channel := range channels {
// channel, err := d.bot.Channel(ch.ID)
// if err != nil {
// d.app.err.Printf("Discord: Couldn't get channel: %v", err)
// return
// }
if channel.Name == d.inviteChannelName {
d.inviteChannelID = channel.ID
found = true
break
}
}
if !found {
d.app.err.Printf("Discord: Couldn't find invite channel \"%s\"", d.inviteChannelName)
return
}
}
// channel, err := d.bot.Channel(d.inviteChannelID)
// if err != nil {
// d.app.err.Printf("Discord: Couldn't get invite channel: %v", err)
// return
// }
inv, err = d.bot.ChannelInviteCreate(d.inviteChannelID, dg.Invite{
// Guild: d.bot.State.Guilds[len(d.bot.State.Guilds)-1],
// Channel: channel,
// Inviter: d.bot.State.User,
MaxAge: ageSeconds,
MaxUses: maxUses,
Temporary: false,
})
if err != nil {
d.app.err.Printf("Discord: Failed to create invite: %v", err)
return
}
inviteURL = "https://discord.gg/" + inv.Code
guild, err := d.bot.Guild(d.guildID)
if err != nil {
d.app.err.Printf("Discord: Failed to get guild: %v", err)
return
}
// FIXME: Fix CSS, and handle no icon
iconURL = guild.IconURL("256")
fmt.Println("GOT ICON", iconURL)
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 {
members, err := d.bot.GuildMembers(
d.guildID,
"",
1000,
)
if err != nil {
d.app.err.Printf("Discord: Failed to get members: %v", err)
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 {
if member.User.Username+"#"+member.User.Discriminator == username {
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)
}
}
return users
}
func (d *DiscordDaemon) NewUser(ID string) (user DiscordUser, ok bool) {
u, err := d.bot.User(ID)
if err != nil {
d.app.err.Printf("Discord: Failed to get user: %v", err)
return
}
user.ID = ID
user.Username = u.Username
user.Contact = true
user.Discriminator = u.Discriminator
channel, err := d.bot.UserChannelCreate(ID)
if err != nil {
d.app.err.Printf("Discord: Failed to create DM channel: %v", err)
return
}
user.ChannelID = channel.ID
ok = true
return
}
func (d *DiscordDaemon) Shutdown() {
d.Stopped = true
d.ShutdownChannel <- "Down"
<-d.ShutdownChannel
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 {
d.app.debug.Printf("Registering choice \"%s\":\"%s\"\n", d.app.storage.lang.Telegram[code].Meta.Name, code)
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()
user, ok := d.tokens[pin]
if !ok || time.Now().After(user.Expiry) {
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)
}
delete(d.tokens, pin)
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)
}
dcUser := d.users[i.Interaction.Member.User.ID]
dcUser.JellyfinID = user.JellyfinID
d.verifiedTokens[pin] = dcUser
delete(d.tokens, pin)
}
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 _, u := range d.app.storage.GetDiscord() {
if u.ID == i.Interaction.Member.User.ID {
u.Lang = code
lang = code
d.app.storage.SetDiscordKey(u.JellyfinID, u)
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 == "" {
channel, err := s.Channel(m.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 != m.ChannelID {
d.app.debug.Printf("Discord: Ignoring message as not in specified channel")
return
}
}
if m.Author.ID == s.State.User.ID {
return
}
sects := strings.Split(m.Content, " ")
if len(sects) == 0 {
return
}
lang := d.app.storage.lang.chosenTelegramLang
if user, ok := d.users[m.Author.ID]; ok {
if _, ok := d.app.storage.lang.Telegram[user.Lang]; ok {
lang = user.Lang
}
}
switch msg := sects[0]; msg {
case "!" + d.app.config.Section("discord").Key("start_command").MustString("start"):
d.msgStart(s, m, lang)
case "!lang":
d.msgLang(s, m, sects, lang)
default:
d.msgPIN(s, m, sects, lang)
}
}
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)
return
}
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)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
return
}
}
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 {
list += fmt.Sprintf("%s: %s\n", code, d.app.storage.lang.Telegram[code].Meta.Name)
}
_, err := s.ChannelMessageSendReply(
m.ChannelID,
list,
m.Reference(),
)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
}
return
}
if _, ok := d.app.storage.lang.Telegram[sects[1]]; ok {
var user DiscordUser
for _, u := range d.app.storage.GetDiscord() {
if u.ID == m.Author.ID {
u.Lang = sects[1]
d.app.storage.SetDiscordKey(u.JellyfinID, u)
user = u
break
}
}
d.users[m.Author.ID] = user
}
}
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 {
d.app.err.Printf("Discord: Failed to get channel: %v", err)
return
}
if channel.Type != dg.ChannelTypeDM {
d.app.debug.Println("Discord: Ignoring message as not a DM")
return
}
} else {
d.app.debug.Println("Discord: Ignoring message as user was not found")
return
}
user, ok := d.tokens[sects[0]]
if !ok || time.Now().After(user.Expiry) {
_, err := s.ChannelMessageSend(
m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("invalidPIN"),
)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
}
delete(d.tokens, sects[0])
return
}
_, err := s.ChannelMessageSend(
m.ChannelID,
d.app.storage.lang.Telegram[lang].Strings.get("pinSuccess"),
)
if err != nil {
d.app.err.Printf("Discord: Failed to send message to \"%s\": %v", m.Author.Username, err)
}
dcUser := d.users[m.Author.ID]
dcUser.JellyfinID = user.JellyfinID
d.verifiedTokens[sects[0]] = dcUser
delete(d.tokens, sects[0])
}
func (d *DiscordDaemon) SendDM(message *Message, userID ...string) error {
channels := make([]string, len(userID))
for i, id := range userID {
channel, err := d.bot.UserChannelCreate(id)
if err != nil {
return err
}
channels[i] = channel.ID
}
return d.Send(message, channels...)
}
func (d *DiscordDaemon) Send(message *Message, channelID ...string) error {
msg := ""
var embeds []*dg.MessageEmbed
if message.Markdown != "" {
msg, embeds = StripAltText(message.Markdown, true)
} else {
msg = message.Text
}
for _, id := range channelID {
var err error
if len(embeds) != 0 {
_, err = d.bot.ChannelMessageSendComplex(
id,
&dg.MessageSend{
Content: msg,
Embed: embeds[0],
},
)
if err != nil {
return err
}
for i := 1; i < len(embeds); i++ {
_, err := d.bot.ChannelMessageSendEmbed(id, embeds[i])
if err != nil {
return err
}
}
} else {
_, err := d.bot.ChannelMessageSend(
id,
msg,
)
if err != nil {
return err
}
}
}
return nil
}
// UserVerified returns whether or not a token with the given PIN has been verified, and the user itself.
func (d *DiscordDaemon) UserVerified(pin string) (user DiscordUser, ok bool) {
user, ok = d.verifiedTokens[pin]
// delete(d.verifiedTokens, pin)
return
}
// AssignedUserVerified returns whether or not a user with the given PIN has been verified, and the token itself.
// Returns false if the given Jellyfin ID does not match the one in the user.
func (d *DiscordDaemon) AssignedUserVerified(pin string, jfID string) (user DiscordUser, ok bool) {
user, ok = d.verifiedTokens[pin]
if ok && user.JellyfinID != jfID {
ok = false
}
// delete(d.verifiedUsers, pin)
return
}
// UserExists returns whether or not a user with the given ID exists.
func (d *DiscordDaemon) UserExists(id string) bool {
c, err := d.app.storage.db.Count(&DiscordUser{}, badgerhold.Where("ID").Eq(id))
return err != nil || c > 0
}
// DeleteVerifiedUser removes the token with the given PIN.
func (d *DiscordDaemon) DeleteVerifiedUser(pin string) {
delete(d.verifiedTokens, pin)
}

1065
email.go

File diff suppressed because it is too large Load Diff

113
exit.go Normal file
View File

@@ -0,0 +1,113 @@
package main
import (
"fmt"
"html/template"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"runtime/debug"
"strings"
"time"
"github.com/robert-nix/ansihtml"
)
// https://gist.github.com/swdunlop/9629168
func identifyPanic() string {
var name, file string
var line int
var pc [16]uintptr
n := runtime.Callers(4, pc[:])
for _, pc := range pc[:n] {
fn := runtime.FuncForPC(pc)
if fn == nil {
continue
}
file, line = fn.FileLine(pc)
name = fn.Name()
if !strings.HasPrefix(name, "runtime.") {
break
}
}
switch {
case name != "":
return fmt.Sprintf("%v:%v", name, line)
case file != "":
return fmt.Sprintf("%v:%v", file, line)
}
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")
if err2 != nil {
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{}{
"Log": logCache,
"SanitizedLog": sanitized,
}
if err != nil {
data["Err"] = fmt.Sprintf("%s %v", identifyPanic(), err)
}
// 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)
}
defer f.Close()
err2 = tmpl.Execute(f, data)
if err2 != nil {
log.Fatalf("Failed to execute template: %v", err2)
}
if err := OpenFile(fpath + ".html"); err != nil {
log.Printf("Failed to open browser, trying text file...")
OpenFile(fpath + ".txt")
}
if TRAY {
QuitTray()
} else {
os.Exit(1)
}
}

51
external.go Normal file
View File

@@ -0,0 +1,51 @@
// +build external
package main
import (
"io/fs"
"log"
"os"
"path/filepath"
"strings"
)
const binaryType = "external"
var localFS dirFS
var langFS dirFS
// When using os.DirFS, even on Windows the separator seems to be '/'.
// func FSJoin(elem ...string) string { return filepath.Join(elem...) }
func FSJoin(elem ...string) string {
sep := "/"
if strings.Contains(elem[0], "\\") {
sep = "\\"
}
path := ""
for _, el := range elem {
path += el + sep
}
return strings.TrimSuffix(path, sep)
}
type dirFS string
func (dir dirFS) Open(name string) (fs.File, error) {
return os.Open(string(dir) + "/" + name)
}
func (dir dirFS) ReadFile(name string) ([]byte, error) {
return os.ReadFile(string(dir) + "/" + name)
}
func (dir dirFS) ReadDir(name string) ([]fs.DirEntry, error) {
return os.ReadDir(string(dir) + "/" + name)
}
func loadFilesystems() {
log.Println("Using external storage")
executable, _ := os.Executable()
localFS = dirFS(filepath.Join(filepath.Dir(executable), "data"))
langFS = dirFS(filepath.Join(filepath.Dir(executable), "data", "lang"))
}

161
go.mod
View File

@@ -1,54 +1,129 @@
module github.com/hrfee/jfa-go
go 1.14
go 1.20
replace github.com/hrfee/jfa-go/docs => ./docs
replace github.com/hrfee/jfa-go/mediabrowser => ./mediabrowser
replace github.com/hrfee/jfa-go/common => ./common
replace github.com/hrfee/jfa-go/ombi => ./ombi
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/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/fsnotify/fsnotify v1.4.9
github.com/gin-contrib/pprof v1.3.0
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
github.com/gin-gonic/gin v1.6.3
github.com/go-chi/chi v4.1.2+incompatible // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/spec v0.20.0 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
github.com/gofrs/uuid v3.3.0+incompatible // indirect
github.com/golang/protobuf v1.4.3
github.com/google/uuid v1.1.2 // indirect
github.com/hrfee/jfa-go/common v0.0.0-20210105184019-fdc97b4e86cc
github.com/hrfee/jfa-go/docs v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/jfa-go/mediabrowser v0.0.0-20201112212552-b6f3cd7c1f71
github.com/hrfee/jfa-go/ombi v0.0.0-20201112212552-b6f3cd7c1f71
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible
github.com/json-iterator/go v1.1.10 // indirect
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e
github.com/lithammer/shortuuid/v3 v3.0.4
github.com/logrusorgru/aurora/v3 v3.0.0
github.com/mailgun/mailgun-go/v4 v4.3.0
github.com/mailru/easyjson v0.7.6 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14
github.com/swaggo/gin-swagger v1.3.0
github.com/swaggo/swag v1.7.0 // indirect
github.com/ugorji/go v1.2.0 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 // indirect
golang.org/x/net v0.0.0-20201224014010-6772e930b67b // indirect
golang.org/x/text v0.3.4 // indirect
golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/ini.v1 v1.62.0
github.com/bwmarrin/discordgo v0.27.1
github.com/emersion/go-autostart v0.0.0-20210130080809-00ed301c8e9a
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.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/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.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
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/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.1.1 // indirect
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
github.com/dgraph-io/badger/v3 v3.2103.1 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // 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/gogo/protobuf v1.3.2 // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/flatbuffers v2.0.0+incompatible // 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/compress v1.13.1 // 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/timshannon/badgerhold/v4 v4.0.2 // 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.opencensus.io v0.23.0 // 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
)

634
go.sum
View File

@@ -1,123 +1,179 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
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/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/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/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 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/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/dgraph-io/badger/v3 v3.2103.1 h1:zaX53IRg7ycxVlkd5pYdCeFp1FynD6qBGQoQql3R3Hk=
github.com/dgraph-io/badger/v3 v3.2103.1/go.mod h1:dULbq6ehJ5K0cGW/1TQ9iSfUk0gbSiToDWmWmTsJ53E=
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
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/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64=
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A=
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/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
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.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
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/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/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
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-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/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-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-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/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-20191128031702-f81c604d8ac2 h1:xLG16iua01X7Gzms9045s2Y2niNpvSY/Zb1oBwgNYZY=
github.com/gin-contrib/static v0.0.0-20191128031702-f81c604d8ac2/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1 h1:plQYoJeO9lI8Ag0xZy7dDF8FMwIOHsQylKjcclknvIc=
github.com/gin-contrib/static v0.0.0-20200815103939-31fb0c56a3d1/go.mod h1:VhW/Ch/3FhimwZb8Oj+qJmdMmoB8r7lmJ5auRjm50oQ=
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 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4=
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.1.2+incompatible h1:fGFk2Gmi/YKXk0OmGfBh0WgmN3XB8lVnEyNz34tQRec=
github.com/go-chi/chi v4.1.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/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 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w=
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 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o=
github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8=
github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg=
github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM=
github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg=
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 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
github.com/go-openapi/spec v0.19.9 h1:9z9cbFuZJ7AcvOHKIY+f6Aevb4vObNDkTEyoMfO7rAc=
github.com/go-openapi/spec v0.19.9/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
github.com/go-openapi/spec v0.19.10 h1:pcNevfYytLaOQuTju0wm6OqcqU/E/pRwuSGigrLTI28=
github.com/go-openapi/spec v0.19.10/go.mod h1:vqK/dIdLGCosfvYsQV3WfC7N3TiZSnGY2RZKoFK7X28=
github.com/go-openapi/spec v0.19.12 h1:OO9WrvhDwtiMY/Opr1j1iFZzirI3JW4/bxNFRcntAr4=
github.com/go-openapi/spec v0.19.12/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
github.com/go-openapi/spec v0.19.13 h1:AcZVcWsrfW7LqyHKVbTZYpFF7jQcMxmAsWrw2p/b9ew=
github.com/go-openapi/spec v0.19.13/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM=
github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA=
github.com/go-openapi/spec v0.20.0 h1:HGLc8AJ7ynOxwv0Lq4TsnwLsWMawHAYiJIFzbcML86I=
github.com/go-openapi/spec v0.20.0/go.mod h1:+81FIL1JwC5P3/Iuuozq3pPE9dXdIEGxFutcFKaVbmU=
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 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE=
github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY=
github.com/go-openapi/swag v0.19.10 h1:A1SWXruroGP15P1sOiegIPbaKio+G9N5TwWTFaVPmAU=
github.com/go-openapi/swag v0.19.10/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc=
github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY=
github.com/go-openapi/swag v0.19.12 h1:Bc0bnY2c3AoF7Gc+IMIAQQsD8fLHjHpc19wXvYuayQI=
github.com/go-openapi/swag v0.19.12/go.mod h1:eFdyEBkTdoAf/9RXBvj4cr1nH7GD8Kzo5HTt47gr72M=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
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/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.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
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/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
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.3.0 h1:nZU+7q+yJoFmwvNgv/LnPUkwPal62+b2xXj0AU1Es7o=
github.com/go-playground/validator/v10 v10.3.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.0 h1:72qIR/m8ybvL8L5TIyfgrigqkrw7kVYAvjEvpT85l70=
github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
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/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/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
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/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529 h1:2voWjNECnrZRbfwXxHB1/j8wa6xdKn85B5NzgVL/pTU=
github.com/golang/glog v0.0.0-20210429001901-424d2337a529/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
@@ -125,177 +181,262 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.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/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
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/flatbuffers v1.12.0/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI=
github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
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 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
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/hrfee/jfa-go/jfapi v0.0.0-20210109010027-4aae65518089 h1:WRk+JAywI8V4u+PBQpdvXBX73yCZxgnLwyIiX7xL+Xc=
github.com/hrfee/jfa-go/jfapi v0.0.0-20210109010027-4aae65518089/go.mod h1:Al1Rd1JGtpS+3KnK8t7+J0CZVDbT86QJrXHR6kZijds=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e h1:OGunVjqY7y4U4laftpEHv+mvZBlr7UGimJXKEGQtg48=
github.com/jordan-wright/email v0.0.0-20200602115436-fd8a7622303e/go.mod h1:Fy2gCFfZhay8jplf/Csj6cyH/oshQTkLQYZbKkcV+SY=
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible h1:CL0ooBNfbNyJTJATno+m0h+zM5bW6v7fKlboKUGP/dI=
github.com/jordan-wright/email v4.0.1-0.20200917010138-e1c00e156980+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/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/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.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
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/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
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/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
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/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 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9 h1:GQE1iatYDRrIidq4Zf/9ZzKWyrTk2sXOYc1JADbkAjQ=
github.com/knz/strtime v0.0.0-20200318182718-be999391ffa9/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius=
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e h1:ViPE0JEOvtw5I0EGUiFSr2VNKGNU+3oBT+oHbDXHbxk=
github.com/knz/strtime v0.0.0-20200924090105-187c67f2bf5e/go.mod h1:4ZxfWkxwtc7dBeifERVVWRy9F9rTU9p0yCDgeCtlius=
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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.13.1 h1:wXr2uRxZTJXHLly6qhJabee5JqIhTRoLBhDOA74hDEQ=
github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
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/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
github.com/labstack/gommon v0.2.9 h1:heVeuAYtevIQVYkGj6A41dtfT91LrvFG220lavpWhrU=
github.com/labstack/gommon v0.2.9/go.mod h1:E8ZTmW9vw5az5/ZyHWCp0Lw4OH2ecsaBP1C/NKavGG4=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lithammer/shortuuid v1.0.0 h1:kdcbvjGVEgqeVeDIRtnANOi/F6ftbKrtbxY+cjQmK1Q=
github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
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/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora/v3 v3.0.0 h1:R6zcoZZbvVcGMvDCKo45A9U/lzYyzl5NfYIvznmDfE4=
github.com/logrusorgru/aurora/v3 v3.0.0/go.mod h1:vsR12bk5grlLvLXAYrBsb5Oc/N+LxAlxggSjiwMnCUc=
github.com/mailgun/mailgun-go v1.1.1 h1:mjMcm4qz+SbjAYbGJ6DKROViKtO5S0YjpuOUxQfdr2A=
github.com/mailgun/mailgun-go v2.0.0+incompatible h1:0FoRHWwMUctnd8KIR3vtZbqdfjpIMxOZgcSa51s8F8o=
github.com/mailgun/mailgun-go/v4 v4.1.3 h1:KLa5EZaOMMeyvY/lfAhWxv9ealB3mtUsMz0O9XmTtP0=
github.com/mailgun/mailgun-go/v4 v4.1.3/go.mod h1:R9kHUQBptF4iSEjhriCQizplCDwrnDShy8w/iPiOfaM=
github.com/mailgun/mailgun-go/v4 v4.2.0 h1:AAt7TwR98Pog7zAYK61SW7ikykFFmCovtix3vvS2cK4=
github.com/mailgun/mailgun-go/v4 v4.2.0/go.mod h1:R9kHUQBptF4iSEjhriCQizplCDwrnDShy8w/iPiOfaM=
github.com/mailgun/mailgun-go/v4 v4.3.0 h1:9nAF7LI3k6bfDPbMZQMMl63Q8/vs+dr1FUN8eR1XMhk=
github.com/mailgun/mailgun-go/v4 v4.3.0/go.mod h1:fWuBI2iaS/pSSyo6+EBpHjatQO3lV8onwqcRy7joSJI=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
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/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/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
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.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/mailru/easyjson v0.7.2 h1:V9ecaZWDYm7v9uJ15RZD6DajMu5sE0hdep0aoDwT9g4=
github.com/mailru/easyjson v0.7.2/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.3 h1:M6wcO9gFHCIPynXGu4iA+NMs//FCgFUWR2jxqV3/+Xk=
github.com/mailru/easyjson v0.7.3/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
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/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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
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/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
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 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
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/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/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858 h1:lgbJiJQx8bXo+eM88AFdd0VxUvaTLzCBXpK+H9poJ+Y=
github.com/pdrum/swagger-automation v0.0.0-20190629163613-c8c7c80ba858/go.mod h1:y02HeaN0visd95W6cEX2NXDv5sCwyqfzucWTdDGEwYY=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
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/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
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/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
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 v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
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 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
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/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
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/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
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/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0=
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.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 h1:e8GC2xDllJZr3omJkm9YfmK0Y56+rMO3cg0JBKNz09s=
github.com/swaggo/swag v1.6.7/go.mod h1:xDhTyuFIujYiN3DKWC/H/83xcfHp+UE/IzWWampG7Zc=
github.com/swaggo/swag v1.6.8 h1:z3ZNcpJs/NLMpZcKqXUsBELmmY2Ocy09JXKx5gu3L4M=
github.com/swaggo/swag v1.6.8/go.mod h1:a0IpNeMfGidNOcm2TsqODUh9JHdHu3kxDA0UlGbBKjI=
github.com/swaggo/swag v1.6.9 h1:BukKRwZjnEcUxQt7Xgfrt9fpav0hiWw9YimdNO9wssw=
github.com/swaggo/swag v1.6.9/go.mod h1:a0IpNeMfGidNOcm2TsqODUh9JHdHu3kxDA0UlGbBKjI=
github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E=
github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo=
github.com/swaggo/swag v1.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.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/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/timshannon/badgerhold/v3 v3.0.0-20210909134927-2b6764d68c1e/go.mod h1:/Seq5xGNo8jLhSbDX3jdbeZrp4yFIpQ6/7n4TjziEWs=
github.com/timshannon/badgerhold/v4 v4.0.2 h1:83OLY/NFnEaMnHEPd84bYtkLipVkjTsMbzQRYbk47g4=
github.com/timshannon/badgerhold/v4 v4.0.2/go.mod h1:rh6RyXLQFsvrvcKondPQQFZnNovpRzu+gS0FlLxYuHY=
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 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.9 h1:SObrQTaSuP8WOv2WNCj8gECiNSJIUvk3Q7N26c96Gws=
github.com/ugorji/go v1.1.9/go.mod h1:chLrngdsg43geAaeId+nXO57YsDdl5OZqd/QtBiD19g=
github.com/ugorji/go v1.1.13/go.mod h1:jxau1n+/wyTGLQoCkjok9r5zFa/FxT6eI5HiHKQszjc=
github.com/ugorji/go v1.2.0 h1:6eXlzYLLwZwXroJx9NyqbYcbv/d93twiOzQLDewE6qM=
github.com/ugorji/go v1.2.0/go.mod h1:1ny++pKMXhLWrwWV5Nf+CbOuZJhMoaFD+0GMFfd8fEc=
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 v0.0.0-20181204163529-d75b2dcb6bc8/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 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.1.9 h1:J/7hhpkQwgypRNvaeh/T5gzJ2gEI/l8S3qyRrdEa1fA=
github.com/ugorji/go/codec v1.1.9/go.mod h1:+SWgpdqOgdW5sBaiDfkHilQ1SxQ1hBkq/R+kHfL7Suo=
github.com/ugorji/go/codec v1.1.13/go.mod h1:oNVt3Dq+FO91WNQ/9JnHKQP2QJxTzoN7wCBFCq1OeuU=
github.com/ugorji/go/codec v1.2.0 h1:As6RccOIlbm9wHuWYMlB30dErcI+4WiKWsYsmPkyrUw=
github.com/ugorji/go/codec v1.2.0/go.mod h1:dXvG35r7zTX6QImXOSFhGMmKtX+wJ7VTWzGvYQGIjBs=
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 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
github.com/urfave/cli/v2 v2.2.0/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/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/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/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
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-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9 h1:umElSU9WZirRdgu2yFHY0ayQkEnKiOC1TtM3fWXFnoU=
golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
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-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-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
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-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -307,66 +448,71 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200923182212-328152dc79b1 h1:Iu68XRPd67wN4aRGGWwwq6bZo/25jR6uu52l/j2KkUE=
golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728 h1:5wtQIAulKU5AbLQOkjxl32UufnIOqgBX72pS0AV14H0=
golang.org/x/net v0.0.0-20200927032502-5d4f70055728/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0 h1:wBouT66WTYFXdxfVdz9sVWARVd/2vfGcmI45D2gj45M=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0 h1:5kGOVHlq0euqwzgTC9Vu15p6fV1Wi0ArVi8da2urnVg=
golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/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-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b h1:iFwSg7t5GZmB/Q5TjiEAsdoLDrdJRC1RiF2WhuV29Qw=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/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-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/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/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
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-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/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-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 h1:sIky/MyNRSHTrdxfsiUSS4WIAMvInbeXljJz+jDjeYE=
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed h1:J22ig1FUekjjkmZUM7pTKixYm8DvrYsvrBZdunYeIuQ=
golang.org/x/sys v0.0.0-20200814200057-3d37ad5750ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c h1:/h0vtH0PyU0xAoZJVcRw1k0Ng+U0JAy3QDiFmppIlIE=
golang.org/x/sys v0.0.0-20200929083018-4d22bbb62b3c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba h1:xmhUJGQGbxlod18iJGqVEp9cHIPLl7QiX2aA3to708s=
golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw=
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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/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-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-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 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@@ -374,32 +520,15 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
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 h1:QjA/9ArTfVTLfEhClDCG7SGrZkZixxWpwNCDiwJfh88=
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.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ=
golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200924182824-0f1c53950d78 h1:3JUoxVhcskhsIDEc7vg0MUUEpmPPN5TfG+E97z/Fn90=
golang.org/x/tools v0.0.0-20200924182824-0f1c53950d78/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20200929191002-f1e51e6b9437 h1:XSFqH8m531iIGazX5lrUC9j3slbwsZ1GFByqdUrLqmI=
golang.org/x/tools v0.0.0-20200929191002-f1e51e6b9437/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06 h1:w9ail9jFLaySAm61Zjhciu0LQ5i8YTy2pimlNLx4uuk=
golang.org/x/tools v0.0.0-20201008184944-d01b322e6f06/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88 h1:ZB1XYzdDo7c/O48jzjMkvIjnC120Z9/CwgDWhePjQdQ=
golang.org/x/tools v0.0.0-20201017001424-6003fad69a88/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9 h1:sEvmEcJVKBNUvgCUClbUQeHOAa9U0I2Ce1BooMvVCY4=
golang.org/x/tools v0.0.0-20201022035929-9cf592e881e9/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201103235415-b653051172e4 h1:Qe0EMgvVYb6tmJhJHljCj3gS96hvSTkGNaIzp/ivq10=
golang.org/x/tools v0.0.0-20201103235415-b653051172e4/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201113202037-1643af1435f3 h1:7R7+wzd5VuLvCNyHZ/MG511kkoP/DBEzkbh8qUsFbY8=
golang.org/x/tools v0.0.0-20201113202037-1643af1435f3/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b h1:Ych5r0Z6MLML1fgf5hTg9p5bV56Xqx9xv9hLgMBATWs=
golang.org/x/tools v0.0.0-20201114224030-61ea331ec02b/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0=
golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee h1:5xKxdl/RhlelmSPaxyVeq5PYSmJ4H14yeQT58qP1F6o=
golang.org/x/tools v0.0.0-20210104081019-d8d6ddbec6ee/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
@@ -407,47 +536,56 @@ golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8T
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
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/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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
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/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.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.60.0 h1:P5ZzC7RJO04094NJYlEnBdFK2wwmnCAy/+7sAzvWs60=
gopkg.in/ini.v1 v1.60.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.61.0 h1:LBCdW4FmFYL4s/vDZD1RQYX7oAR6IjujCYgMdbHBR10=
gopkg.in/ini.v1 v1.61.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
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/base.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>

52
html/account-linking.html Normal file
View File

@@ -0,0 +1,52 @@
{{ if .discordEnabled }}
<div id="modal-discord" class="modal">
<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 pin"></h1>
<div class="row center">
<a class="my-5 hover:underline">
<span class="mr-2">{{ .strings.joinTheServer }}</span>
<span id="discord-invite"></span>
</a>
</div>
<span class="button ~info @low full-width center mt-4" id="discord-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
{{ if .telegramEnabled }}
<div id="modal-telegram" class="modal">
<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 pin"></p>
<a class="subheading link-center" href="{{ .telegramURL }}" target="_blank">
<span class="shield ~info mr-4">
<span class="icon">
<i class="ri-telegram-line"></i>
</span>
</span>
&#64;{{ .telegramUsername }}
</a>
<span class="button ~info @low full-width center mt-4" id="telegram-waiting">{{ .strings.success }}</span>
</div>
</div>
{{ end }}
{{ if .matrixEnabled }}
<div id="modal-matrix" class="modal">
<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 @low full-width center mt-4" id="matrix-send">{{ .strings.submit }}</span>
</div>
</div>
{{ end }}

View File

@@ -1,140 +1,306 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/base.css">
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
<script>
window.URLBase = "{{ .urlBase }}";
window.notificationsEnabled = {{ .notifications }};
window.emailEnabled = {{ .email_enabled }};
window.emailEnabled = {{ .emailEnabled }};
window.telegramEnabled = {{ .telegramEnabled }};
window.discordEnabled = {{ .discordEnabled }};
window.matrixEnabled = {{ .matrixEnabled }};
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="">
<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">
<label>
<input type="submit" class="unfocused">
<span class="button ~urge !normal full-width center supra submit">{{ .strings.login }}</span>
</label>
</form>
</div>
{{ template "login-modal.html" . }}
<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">
<span class="heading">{{ .strings.aboutProgram }} <span class="modal-close">&times;</span></span>
<img src="/banner.svg" class="mt-1" alt="jfa-go banner">
<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>
<p><a href="https://github.com/hrfee/jfa-go/blob/main/LICENSE">Available under the MIT License.</a></p>
<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>{{ .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>
<p>{{ .strings.buildTime }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .buildTime }}</span></p>
<p>{{ .strings.builtBy }} <span class="text-black dark:text-white font-mono bg-inherit">{{ .builtBy }}</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. Font "Hanken Grotesk" available under SIL OFL 1.1 License.</a></p>
<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="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-8">
<div class="row">
<div class="col">
<label class="label supra" for="extend-expiry-months">{{ .strings.inviteMonths }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-months">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="extend-expiry-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-days">
<option>0</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<label class="label supra" for="extend-expiry-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="extend-expiry-hours">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="extend-expiry-minutes">{{ .strings.inviteMinutes }}</label>
<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 @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</div>
</form>
</div>
<div id="modal-announce" class="modal">
<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 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 @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 @low mb-2 mt-4">
<label class="label supra" for="textarea-announce">{{ .strings.message }}</label>
<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 @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 @low center supra submit">{{ .strings.send }}</span>
</label>
<span class="button ~info @low center supra" id="save-announce">{{ .strings.saveAsTemplate }}</span>
</div>
</div>
<div class="col card ~neutral @low">
<span class="subheading supra">{{ .strings.preview }}</span>
<div class="mt-8" id="announce-preview"></div>
</div>
</div>
</form>
</div>
<div id="modal-customize" class="modal">
<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 my-4">{{ .strings.customizeMessagesDescription }}</p>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{{ .strings.name }}</th>
<th class="table-inline justify-center">{{ .strings.reset }}</th>
<th>{{ .strings.edit }}</th>
</tr>
</thead>
<tbody id="customize-list"></tbody>
</table>
</div>
</div>
</div>
<div id="modal-editor" class="modal">
<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 card ~neutral @low">
<span class="label supra" for="editor-variables" id="label-editor-variables">{{ .strings.variables }}</span>
<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 @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-2">
<input type="submit" class="unfocused">
<span class="button ~urge @low full-width center supra submit">{{ .strings.submit }}</span>
</label>
</div>
</div>
<div class="col card ~neutral @low">
<span class="subheading supra">{{ .strings.preview }}</span>
<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>
@@ -143,128 +309,336 @@
</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="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 class="mt-2">
<a id="update-version"></a> (<span class="font-mono bg-inherit" id="update-commit"></span>)
</h2>
<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 @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="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-2">
<span class="icon">
<i class="ri-telegram-line"></i>
</span>
</span>
&#64;<span id="telegram-username">
</a>
<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="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 @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="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 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 @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" 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">
<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="top-4 right-4 absolute">
<a class="button ~info" href="/my/account"><i class="ri-account-circle-fill mr-2"></i>{{ .strings.myAccount }}</a>
</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">
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="create-days">
<option>0</option>
</select>
<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>
</label>
<label class="col ml-2">
<input type="radio" name="duration" class="unfocused" id="radio-user-expiry">
<span class="button ~neutral @low supra full-width center">{{ .strings.userExpiry }}</span>
</label>
</div>
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="create-hours">
<option>0</option>
</select>
<div id="inv-duration">
<div class="row">
<div class="col">
<label class="label supra" for="create-months">{{ .strings.inviteMonths }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-months">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="create-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-days">
<option>0</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<label class="label supra" for="create-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-hours">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="create-minutes">
<option>0</option>
</select>
</div>
</div>
</div>
</div>
<label class="label supra" for="create-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral !normal mb-1 mt-half">
<select id="create-minutes">
<option>0</option>
</select>
<div id="user-expiry" class="unfocused">
<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-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 @low mb-2 mt-4">
<select id="user-months">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="user-days">{{ .strings.inviteDays }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-days">
<option>0</option>
</select>
</div>
</div>
</div>
<div class="row">
<div class="col">
<label class="label supra" for="user-hours">{{ .strings.inviteHours }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-hours">
<option>0</option>
</select>
</div>
</div>
<div class="col">
<label class="label supra" for="user-minutes">{{ .strings.inviteMinutes }}</label>
<div class="select ~neutral @low mb-2 mt-4">
<select id="user-minutes">
<option>0</option>
</select>
</div>
</div>
</div>
</div>
<div class="col">
<label class="label supra" for="create-label"> {{ .strings.label }}</label>
<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">
<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">
<input type="email" id="create-send-to" class="input ~neutral !normal mr-1" placeholder="example@example.com">
<label for="create-send-to-enabled" class="button ~neutral !normal">
<div class="flex-expand mb-2 mt-4">
{{ if .discordEnabled }}
<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 @low mr-2" placeholder="example@example.com">
{{ end }}
<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">
<span class="heading">{{ .strings.accounts }}</span>
<div class="fr">
<span class="button ~neutral !normal" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
<span class="button ~urge !normal" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
<span class="button ~critical !normal" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
<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>
</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>{{ .strings.lastActiveTime }}</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 class="text-center-i grid gap-4 place-items-stretch accounts-header-telegram">Telegram</th>
{{ end }}
{{ if .matrixEnabled }}
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-matrix">Matrix</th>
{{ end }}
{{ if .discordEnabled }}
<th class="text-center-i grid gap-4 place-items-stretch accounts-header-discord">Discord</th>
{{ end }}
<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>
@@ -273,22 +647,32 @@
</div>
</div>
<div id="tab-settings" class="unfocused">
<div class="card ~neutral !low settings overflow">
<span class="heading">{{ .strings.settings }}</span>
<div class="fr">
<span class="button ~neutral !normal unfocused" id="settings-save">{{ .strings.settingsSave }}</span>
</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="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 @low ml-2 my-2">
<input type="checkbox" id="settings-advanced-enabled" aria-label="Advanced settings enabled">
<span class="ml-2">{{ .strings.advancedSettings }} </span>
</label>
</div>
<div class="card ~neutral !normal col overflow" id="settings-panel"></div>
<div>
<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="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 @low col overflow" id="settings-panel"></div>
</div>
</div>
</div>
</div>
<script src="js/admin.js" type="module"></script>
<script src="{{ .urlBase }}/js/admin.js" type="module"></script>
</body>
</html>

45
html/crash.html Normal file
View File

@@ -0,0 +1,45 @@
<!doctype html>
<html lang="en">
<head>
<link inline rel="stylesheet" type="text/css" href="bundle.css">
{{ template "header.html" . }}
<title>Crash report</title>
</head>
<body>
<div class="page-container">
<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>
{{ 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>
</body>
</html>

18
html/create-success.html Normal file
View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .strings.successHeader }} - jfa-go</title>
</head>
<body class="section">
<div class="page-container">
<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>
</body>
</html>

View File

@@ -5,6 +5,48 @@
window.invalidPassword = "{{ .strings.reEnterPasswordInvalid }}";
window.URLBase = "{{ .urlBase }}";
window.code = "{{ .code }}";
window.language = "{{ .langName }}";
window.messages = JSON.parse({{ .notifications }});
window.confirmation = {{ .confirmation }};
window.userExpiryEnabled = {{ .userExpiry }};
window.userExpiryMonths = {{ .userExpiryMonths }};
window.userExpiryDays = {{ .userExpiryDays }};
window.userExpiryHours = {{ .userExpiryHours }};
window.userExpiryMinutes = {{ .userExpiryMinutes }};
window.userExpiryMessage = {{ .userExpiryMessage }};
window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }};
window.telegramPIN = "{{ .telegramPIN }}";
window.emailRequired = {{ .emailRequired }};
window.discordEnabled = {{ .discordEnabled }};
window.discordRequired = {{ .discordRequired }};
window.discordPIN = "{{ .discordPIN }}";
window.discordInviteLink = {{ .discordInviteLink }};
window.discordServerName = "{{ .discordServerName }}";
window.matrixEnabled = {{ .matrixEnabled }};
window.matrixRequired = {{ .matrixRequired }};
window.matrixUserID = "{{ .matrixUser }}";
window.captcha = {{ .captcha }};
window.reCAPTCHA = {{ .reCAPTCHA }};
window.reCAPTCHASiteKey = "{{ .reCAPTCHASiteKey }}";
window.userPageEnabled = {{ .userPageEnabled }};
window.userPageAddress = "{{ .userPageAddress }}";
</script>
{{ if .passwordReset }}
<script src="js/pwr.js" type="module"></script>
{{ else }}
<script src="js/form.js" type="module"></script>
{{ if .reCAPTCHA }}
<script>
var reCAPTCHACallback = () => {
const el = document.getElementsByClassName("g-recaptcha")[0];
grecaptcha.render(el, {
"sitekey": window.reCAPTCHASiteKey,
"theme": document.documentElement.classList.contains("dark") ? "dark" : "light"
});
}
</script>
<script src="https://www.google.com/recaptcha/api.js?onload=reCAPTCHACallback&render=explicit" async defer></script>
{{ end }}
{{ end }}
{{ end }}

View File

@@ -1,60 +1,130 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/base.css">
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
{{ 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>
{{ if .userPageEnabled }}<p class="content mb-4" id="modal-success-user-page-area" my-account-term="{{ .strings.myAccount }}">{{ .strings.userPageSuccessMessage }}</p>{{ end }}
<a class="button ~urge @low full-width center supra submit" href="{{ .jfLink }}" id="create-success-button">{{ .strings.continue }}</a>
</div>
</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" id="lang-list">
</div>
<div id="modal-confirmation" class="modal">
<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>
</span>
<div class="page-container">
<div class="card ~neutral !low">
<div class="row baseline">
<span class="col heading">{{ .strings.createAccountHeader }}</span>
<span class="col subheading"> {{ .helpMessage }}</span>
</div>
{{ template "account-linking.html" . }}
<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 class="row">
<div class="col">
<form class="card ~neutral !normal" id="form-create" href="">
</span>
</div>
<div id="notification-box"></div>
<div class="page-container">
<div class="card dark:~d_neutral @low">
<div class="flex flex-col md:flex-row gap-3 inline align-baseline">
<span class="heading mr-5">
{{ if .passwordReset }}
{{ .strings.passwordReset }}
{{ else }}
{{ .strings.createAccountHeader }}
{{ end }}
</span>
<span class="subheading">
{{ if .passwordReset }}
{{ .strings.enterYourPassword }}
{{ else }}
{{ .helpMessage }}
{{ end }}
</span>
</div>
<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 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 1high 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 @low full-width center mb-4" id="link-telegram">{{ .strings.linkTelegram }} {{ if .telegramRequired }}({{ .strings.required }}){{ end }}</span>
{{ end }}
{{ if .discordEnabled }}
<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 @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-4 unfocused">
<input type="checkbox" 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-4 unfocused">
<input type="checkbox" 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-4 unfocused">
<input type="checkbox" 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-4 unfocused">
<input type="checkbox" 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 1high 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 1high 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">{{ .strings.createAccountButton }}</span>
<span class="button ~urge @low full-width center supra submit">
{{ if .passwordReset }}
{{ .strings.reset }}
{{ else }}
{{ .strings.createAccountButton }}
{{ end }}
</span>
</label>
</form>
</div>
<div class="col">
<div class="card ~neutral !normal">
<span class="label supra" for="inv-uses">{{ .strings.passwordRequirementsHeader }}</span>
<div class="flex-initial">
<div class="card ~neutral @low mb-4">
<span class="label supra">{{ .strings.passwordRequirementsHeader }}</span>
<ul>
{{ range $key, $value := .requirements }}
<li class="" id="requirement-{{ $key }}" min="{{ $value }}">
@@ -63,8 +133,17 @@
{{ end }}
</ul>
</div>
{{ if .captcha }}
<div class="card ~neutral @low mb-4">
<span class="label supra mb-2">CAPTCHA {{ if not .reCAPTCHA }}<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>{{ end }}</span>
<div id="captcha-img" class="mt-2 mb-2 {{ if .reCAPTCHA }}g-recaptcha{{ end }}"></div>
{{ if not .reCAPTCHA }}
<input class="field ~neutral @low" id="captcha-input" class="mt-2" placeholder="CAPTCHA">
{{ end }}
</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>
@@ -73,4 +152,3 @@
{{ template "form-base" . }}
</body>
</html>

View File

@@ -1,6 +1,7 @@
<meta charset="utf-8">
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="Description" content="jfa-go, a better way to manage Jellyfin users.">
<meta name="color-scheme" content="dark light">
<link rel="apple-touch-icon" sizes="180x180" href="{{ .urlBase }}/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{{ .urlBase }}/favicon-32x32.png">

View File

@@ -1,17 +1,19 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/base.css">
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/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>

25
html/login-modal.html Normal file
View File

@@ -0,0 +1,25 @@
<div id="modal-login" class="modal">
<div class="my-[10%] row items-stretch relative mx-auto w-[40%] lg:w-[60%]">
{{ if index . "LoginMessageEnabled" }}
{{ if .LoginMessageEnabled }}
<div class="card mx-2 flex-initial w-[100%] xl:w-[35%] mb-4 xl:mb-0 dark:~d_neutral @low content">
{{ .LoginMessageContent }}
</div>
{{ end }}
{{ end }}
<form class="card mx-2 flex-auto form-login w-[100%] xl:w-[55%] mb-0" href="">
<span class="heading">{{ .strings.login }}</span>
<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 @low full-width center supra submit">{{ .strings.login }}</span>
{{ if index . "pwrEnabled" }}
{{ if .pwrEnabled }}
<span class="button ~info @low full-width center supra submit my-2" id="modal-login-pwr">{{ .strings.resetPassword }}</span>
{{ end }}
{{ end }}
</label>
</form>
</div>
</div>

45
html/password-reset.html Normal file
View File

@@ -0,0 +1,45 @@
<!DOCTYPE html>
<html lang="en" class="{{ .cssClass }}">
<head>
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .strings.passwordReset }} - jfa-go</title>
</head>
<body class="section">
{{ if .success }}
<div id="notification-box">
<span id="copy-notification" class="unfocused">{{ .strings.copied }}</span>
</div>
{{ end }}
<div class="page-container">
<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-4">
{{ if .success }}
{{ if .ombiEnabled }}
{{ .strings.youCanLoginOmbi }}
{{ else }}
{{ .strings.youCanLogin }}
{{ end }}
{{ else }}
{{ .strings.tryAgain }}
{{ end }}
</p>
{{ if .success }}
<aside class="aside ~warning">
{{ .strings.changeYourPassword }}
</aside>
<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>
</div>
<script src="{{ .urlBase }}/js/pwr-pin.js" type="module"></script>
</body>
</html>

View File

@@ -1,374 +1,502 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="light">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-alpha3/dist/js/bootstrap.min.js" integrity="sha384-t6I8D5dJmMXjCsRLhSzCltuhNZg6P10kE0m0nAncLUjH6GeYLhRU1zfLoW3QNQDF" crossorigin="anonymous"></script>
<style>
.card-body {
width: 100%;
height: 100%;
padding: 10%;
}
.slider {
width: 100%;
height: 100%;
display: flex;
overflow-x: hidden;
-webkit-overflow-scrolling: none;
scroll-snap-type: x mandatory;
}
.slider > div {
scroll-snap-align: start;
}
.slide {
width: 100%;
flex-shrink: 0;
height: 100%;
margin: 10%;
}
</style>
<title>Setup - jfa-go</title>
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
{{ template "header.html" . }}
<title>{{ .lang.Strings.pageTitle }}</title>
</head>
<body>
<div class="pageContainer">
<div class="container">
<body class="max-w-full overflow-x-hidden section">
<div id="notification-box"></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" id="lang-list">
</div>
</div>
</span>
</div>
<div class="page-container" id="page-container">
<div class="card ~neutral @low mb-2">
<div class="row">
<div class="col-sm"></div>
<div id="setupCarousel" class="col-md-auto slider">
<div class="slide card text-center" id="page-1">
<div class="card-body">
<h5 class="card-title">Welcome!</h5>
<p class="card-text">
You'll need to do a few things to start using jfa-go. Click below to get started, or quit and edit the config file manually.
</p>
<a class="btn btn-primary nextButton" href="#page-2">Get Started</a>
<img class="banner header" src="banner.svg" alt="jfa-go" />
</div>
<div class="row col flex center">
<span class="heading welcome">{{ .lang.StartPage.welcome }}</span>
</div>
<div class="row col flex center">
<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 @low next">{{ .lang.StartPage.start }}</span>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Language.title }}</span>
<p class="content my-2" id="language-description"></p>
<label class="label">
<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-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-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 @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<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-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" class="mr-2" id="advanced-tls"><span>{{ .lang.General.useHTTPS }}</span>
</label>
<p class="support mb-2 mt-1">{{ .lang.General.useHTTPSNotice }}</p>
<label class="label">
<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-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 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 @low mt-4 mb-2">
<select id="updates-channel">
<option value="stable">{{ .lang.Updates.stable }}</option>
<option value="unstable">{{ .lang.Updates.unstable }}</option>
</select>
</div>
<div class="card-footer">
<small>Note: Make sure you are accessing this page through HTTPS, or on a private network.</small>
</label>
</div>
<div class="col">
<label class="label">
<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-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-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 @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>
</select>
</div>
</label>
</div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<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-2 unfocused">
<span class="heading">{{ .lang.Login.title }}</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-4 pb-4">
<input type="checkbox" class="mr-2" id="ui-admin_only" checked><span>{{ .lang.Login.adminOnly }}</span>
</label>
<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-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 @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 @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 @low back">{{ .lang.Strings.back }}</span>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.JellyfinEmby.title }}</span>
<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 @low mt-4">
<select id="jellyfin-type">
<option value="jellyfin">Jellyfin</option>
<option value="emby">Emby</option>
</select>
</div>
<p class="support mb-2 mt-1">{{ .lang.JellyfinEmby.embyNotice }}</p>
</label>
<label class="label">
<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-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 @low mt-4 mb-2" placeholder="{{ .lang.Strings.password }}">
</label>
</div>
<div class="col">
<label class="label">
<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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<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-2 unfocused">
<span class="heading">{{ .lang.Ombi.title }}</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-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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.Messages.title }}</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-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-4">
<input type="radio" class="mr-2" name="email-24h" value="true" checked><span>{{ .lang.Strings.time24h }}</span>
</label>
<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 my-2" id="email-description"></p>
<div class="row">
<div class="col">
<label class="label">
<span>{{ .lang.Email.method }}</span>
<div class="select ~neutral @low mt-4 mb-2">
<select id="email-method">
<option value="">{{ .lang.Strings.disabled }}</option>
<option value="smtp">SMTP</option>
<option value="mailgun">Mailgun</option>
</select>
</div>
</label>
<label class="row switch">
<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-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-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="slide card" id="page-2">
<div class="card-body">
<h5 class="card-title">Login</h5>
<p class="card-text">
To access the admin page, you'll need to login. Choose how below.
<ul>
<li><b>Authorize through Jellyfin: </b>Checks credentials with Jellyfin, allowing you to share login details and grant multiple users access.</li>
<li><b>Username & Password: </b>Set your own username and password manually.</li>
</ul>
<div class="form-check" id="jfAuthFormGroup">
<input class="form-check-input" type="radio" name="auth" id="jfAuthRadio" value="jfAuth" checked>
<label class="form-check-label" for="jfAuthRadio">
Authorize through Jellyfin
</label>
</div>
<div id="adminOnlyArea">
<div class="form-check" style="margin-left: 1rem;">
<input type="checkbox" class="form-check-input" id="jfAuthAdminOnly" checked>
<label for="jfAuthAdminOnly" class="form-check-label">Allow admin users only</label>
<div class="col">
<div id="email-smtp">
<p class="text-2xl font-semibold mb-2">SMTP</p>
<label class="label">
<span>{{ .lang.Email.encryption }}</span>
<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>
</select>
</div>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="auth" id="manualAuthRadio" value="manualAuth">
<label class="form-check-label" for="manualAuthRadio">
Manual username &amp; password
</label>
</div>
<div id="manualAuthArea">
<div class="form-group">
<label for="manualAuthUsername">Username</label>
<input type="text" class="form-control" id="manualAuthUsername" placeholder="Username">
</div>
<div class="form-group">
<label for="manualAuthPassword">Password</label>
<input type="password" class="form-control" id="manualAuthPassword" placeholder="Password">
</div>
<div class="form-group">
<label for="manualAuthEmail">Email (Optional)</label>
<input type="email" class="form-control" id="manualAuthEmail" placeholder="example@example.com">
<small class="form-text text-muted">Your email address is only required if you want to recieve activity notifications.</small>
</div>
</div>
</p>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-1">Back</a>
<a class="btn btn-primary nextButton" href="#page-3">Next</a>
</div>
</label>
<label class="label">
<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-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-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-4">{{ .lang.Strings.password }}</span>
<input type="password" class="input ~neutral @low mt-4 mb-2" id="smtp-password">
</label>
</div>
</div>
<div class="slide card" id="page-3">
<div class="card-body">
<h5 class="card-title">Jellyfin</h5>
<p class="card-text">
jfa-go needs admin access so that it can create users, as this is currently not permitted via API tokens.
You should create a separate account for it, checking 'Allow this user to manage the server'. You can disable everything else. Once done, enter the credentials here.
<div class="form-group">
<label for="jfHost">Host (For internal use)</label>
<input type="url" class="form-control" id="jfHost" placeholder="http://jellyf.in:443" required>
</div>
<div class="form-group">
<label for="jfPublicHost">Public Host (For access by users)</label>
<input type="url" class="form-control" id="jfPublicHost" placeholder="Leave blank to use the above address.">
</div>
<div class="form-group">
<label for="jfUser">Username</label>
<input type="text" class="form-control" id="jfUser" placeholder="Username" required>
</div>
<div class="form-group">
<label for="jfPassword">Password</label>
<input type="password" class="form-control" id="jfPassword" placeholder="Password" required>
</div>
<div style="margin-top: 1rem;">
<button class="btn btn-secondary" id="jfTestButton">Test</button>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-2">Back</a>
<a class="btn btn-primary nextButton disabled" id="jfNextButton" aria-disabled="true" href="#page-4">Next</a>
</div>
</div>
</div>
</div>
<div class="slide card" id="page-4">
<div class="card-body">
<h5 class="card-title">Email</h5>
<p class="card-text">jfa-go is capable of sending a PIN code when a user tries to reset their password on Jellyfin. One can also choose to send an invite code directly to an email address. This can be done through SMTP or through <a href="https://www.mailgun.com/">Mailgun's</a> API.
<div class="form-group">
<div class="form-check" id="emailDisabled">
<input class="form-check-input" type="radio" name="email" id="emailDisabledRadio" value="emailDisabled">
<label class="form-check-label" for="emailDisabledRadio">
Disabled
</label>
</div>
<div class="form-check" id="emailSMTP">
<input class="form-check-input" type="radio" name="email" id="emailSMTPRadio" value="emailSMTP" checked>
<label class="form-check-label" for="emailSMTPRadio">
SMTP
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="email" id="emailMailgunRadio" value="emailMailgun">
<label class="form-check-label" for="emailMailgunRadio">
Mailgun API
</label>
</div>
</div>
<div id="emailSMTPArea">
<div class="form-group form-check form-switch">
<input type="checkbox" class="form-check-input" id="emailSSL_TLS" checked>
<label for="emailSSL_TLS" class="form-check-label" id="emailSSL_TLSLabel">Use SSL/TLS</label>
<small class="form-text text-muted">Note: SSL/TLS usually uses port 465, whereas STARTTLS usually uses 587.</small>
</div>
<div class="form-group form-row">
<div class="col">
<input type="text" class="form-control" id="emailSMTPServer" placeholder="SMTP Server Address">
</div>
<div class="col">
<input type="number" class="form-control" id="emailSMTPPort" placeholder="Port">
</div>
</div>
<div class="form-group form-row">
<div class="col">
<input type="email" class="form-control" id="emailSMTPAddress" placeholder="jellyfin@jellyf.in">
</div>
<div class="col">
<input type="password" class="form-control" id="emailSMTPPassword" placeholder="Password">
</div>
</div>
</div>
<div id="emailMailgunArea">
<div class="form-group">
<input type="url" class="form-control" id="emailMailgunURL" placeholder="API URL">
</div>
<div class="form-group">
<input type="password" class="form-control" id="emailMailgunKey" placeholder="API Key">
</div>
<div class="form-group">
<input type="email" class="form-control" id="emailMailgunAddress" placeholder="jellyfin@jellyf.in">
</div>
</div>
<div id="emailCommonArea">
<h5 class="card-title">Notifications</h5>
<p class="card-text">Enabling notifications will allow you to choose (per-invite) to recieve emails when an invite expires, or when a new user is created. If you chose to use Manual auth instead of Jellyfin auth previously, make sure you provided an email address.</p>
<div class="form-check">
<input type="checkbox" class="form-check-input" id="notificationsEnabled">
<label for="notificationsEnabled" class="form-check-label">Enabled</label>
</div>
</div>
</p>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-3">Back</a>
<a class="btn btn-primary nextButton" id="emailNextButton" href="#page-5">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-5">
<div class="card-body">
<h5 class="card-title">Email</h5>
<p class="card-text">Just a few more things to get your emails looking great.
<div class="form-group">
<label for="emailSender">Sender: The name shown when a user receives an email.</label>
<input type="text" class="form-control" id="emailSender" value="Jellyfin">
</div>
<div class="form-group">
<label for="emailDateFormat">Date Format: Follows <a target="_blank" href="https://strftime.org/">strftime</a> format.</label>
<input type="text" class="form-control" id="emailDateFormat" value="%d/%m/%y">
</div>
<div class="form-group form-check">
<input class="form-check-input" type="radio" name="time" id="email24hTimeRadio" value="email24hTime" checked>
<label class="form-check-label" for="email24hTimeRadio">24h time</label>
</div>
<div class="form-group form-check">
<input class="form-check-input" type="radio" name="time" id="email12hTimeRadio" value="email12hTime">
<label class="form-check-label" for="email12hTimeRadio">12h time</label>
</div>
<div class="form-group">
<label for="emailMessage">Message: Short message displayed at the bottom of emails.</label>
<input type="text" class="form-control" id="emailMessage" value="Need help? Contact me.">
</div>
</p>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-4">Back</a>
<a class="btn btn-primary nextButton" href="#page-6">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-6">
<div class="card-body">
<h5 class="card-title">Password Resets</h5>
<p class="card-text">
When a user tries to reset their password in jellyfin, it informs them that a file has been created, named "passwordreset*.json" where * is a number. jfa-go will then read this file, and send the PIN to the user's email. Try it now, and put the folder that it informs you it put the file in below. Also, if enter a custom email subject if you don't like the default one.
</p>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="pwrEnabled" value="enabled">
<label class="form-check-label" for="pwrEnabled">Enabled</label>
</div>
<div id="pwrArea">
<div class="form-group">
<label for="pwrJfPath">Path to Jellyfin</label>
<input type="text" class="form-control" id="pwrJfPath" placeholder="Folder">
</div>
<div class="form-group">
<label for="pwrSubject">Email Subject</label>
<input type="text" class="form-control" id="pwrSubject" value="Password Reset - Jellyfin">
</div>
</div>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-5">Back</a>
<a class="btn btn-primary nextButton" href="#page-7">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-7">
<div class="card-body">
<h5 class="card-title">Invite Emails</h5>
<p class="card-text">
Allows you to send an invite code directly to a specified email address.
Since you'll most likely being running this behind a reverse proxy, the program has no way of knowing the address it will be accessed from. This is needed for sending emails with links. Write your URL Base with the protocol and append '/invite', e.g:
<ul>
<li>On the local network, you might use <a href="#">http://localhost:8056/invite</a></li>
<li>Exposed to the internet, you might use <a href="#">https://accounts.jellyf.in/invite</a></li>
</ul>
</p>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="invEnabled" value="enabled" checked>
<label class="form-check-label" for="invEnabled">Enabled</label>
</div>
<div id="invArea">
<div class="form-group">
<label for="invURLBase">URL Base</label>
<input type="url" class="form-control" id="invURLBase" placeholder="https://accounts.jellyf.in/invite">
</div>
<div class="form-group">
<label for="invSubject">Subject</label>
<input type="text" class="form-control" id="invSubject" value="Invite - Jellyfin">
</div>
</div>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-6">Back</a>
<a class="btn btn-primary nextButton" href="#page-8">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-8">
<div class="card-body">
<h5 class="card-title">Password Validation</h5>
<p class="card-text">
Enabling this will display a set of password requirements on the create account page, such as minimum length, uppercase characters, special characters, etc.
</p>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="valEnabled" value="enabled">
<label class="form-check-label" for="valEnabled">Enabled</label>
</div>
<div id="valArea">
<div class="form-group">
<label for="valLength">Minimum Length</label>
<input type="number" class="form-control" id="valLength" value="8">
</div>
<div class="form-group">
<label for="valUpper">Minimum number of uppercase characters</label>
<input type="number" class="form-control" id="valUpper" value="1">
</div>
<div class="form-group">
<label for="valLower">Minimum number of lowercase characters</label>
<input type="number" class="form-control" id="valLower" value="0">
</div>
<div class="form-group">
<label for="valNumber">Minimum number of numbers</label>
<input type="number" class="form-control" id="valNumber" value="0">
</div>
<div class="form-group">
<label for="valSpecial">Minimum number of special characters</label>
<input type="number" class="form-control" id="valSpecial" value="0">
</div>
</div>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" id="valBackButton" href="#page-7">Back</a>
<a class="btn btn-primary nextButton" href="#page-9">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-9">
<div class="card-body">
<h5 class="card-title">Help Messages</h5>
<p class="card-text">
Just a few little messages that will display in various places. Leave these alone if you want.
</p>
<div class="form-group">
<label for="msgContact">Contact message: Displays at bottom of all pages (except admin).</label>
<input id="msgContact" type="text" class="form-control" value="Need help? Contact me.">
</div>
<div class="form-group">
<label for="msgHelp">Help message: Displays when a user is creating an account.</label>
<input id="msgHelp" type="text" class="form-control" value="Enter your details to create an account.">
</div>
<div class="form-group">
<label for="msgSuccess">Success message: Displays when a user successfully creates an account, just above a button taking the user to Jellyfin.</label>
<input id="msgSuccess" type="text" class="form-control" value="Your account has been created. Click below to continue to Jellyfin.">
</div>
<div class="btn-group float-right" role="group" aria-label="Back/Next buttons">
<a class="btn btn-secondary backButton" href="#page-8">Back</a>
<a class="btn btn-primary nextButton" href="#page-10">Next</a>
</div>
</div>
</div>
<div class="slide card" id="page-10">
<div class="card-body text-center">
<h5 class="card-title">Finished!</h5>
<p class="card-text">
Press the button below to submit your settings. The program will restart. Once it's done, refresh this page.
</p>
<button id="submitButton" class="btn btn-primary">Submit</button>
<div id="email-mailgun">
<p class="text-2xl font-semibold mb-2">Mailgun</p>
<label class="label">
<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-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 class="col-sm"></div>
</div>
<section class="section ~neutral banner footer flex-expand middle">
<span class="button ~neutral @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.Notifications.title }}</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 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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.InviteEmails.title }}</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-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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div id="password-resets" class="card ~neutral @low mb-2 unfocused related-to-email">
<span class="heading">{{ .lang.PasswordResets.title }}</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-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" 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" 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-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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.PasswordValidation.title }}</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-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-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-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-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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<div class="card ~neutral @low mb-2 unfocused">
<span class="heading">{{ .lang.HelpMessages.title }}</span>
<p class="content my-2">{{ .lang.HelpMessages.description }}</p>
<label class="label">
<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-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-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-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 @low back">{{ .lang.Strings.back }}</span>
<div>
<span class="button ~urge @low next">{{ .lang.Strings.next }}</span>
</div>
</section>
</div>
<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 my-2">{{ .lang.EndPage.restartMessage }}</p>
</div>
<div class="row col flex center">
<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>
<script src="js/setup.js"></script>
<script>
window.langFile = JSON.parse({{ .language }});
window.messages = JSON.parse({{ .messages }});
</script>
<script src="js/setup.js" type="module"></script>
</body>
</html>
</html>

158
html/user.html Normal file
View File

@@ -0,0 +1,158 @@
<html lang="en" class="light">
<head>
<link rel="stylesheet" type="text/css" href="{{ .urlBase }}/css/{{ .cssVersion }}bundle.css">
<script>
window.URLBase = "{{ .urlBase }}";
window.notificationsEnabled = {{ .notifications }};
window.ombiEnabled = {{ .ombiEnabled }};
window.langFile = JSON.parse({{ .language }});
window.pwrEnabled = {{ .pwrEnabled }};
window.linkResetEnabled = {{ .linkResetEnabled }};
window.language = "{{ .langName }}";
window.telegramEnabled = {{ .telegramEnabled }};
window.telegramRequired = {{ .telegramRequired }};
window.telegramUsername = {{ .telegramUsername }};
window.telegramURL = {{ .telegramURL }};
window.emailEnabled = {{ .emailEnabled }};
window.emailRequired = {{ .emailRequired }};
window.discordEnabled = {{ .discordEnabled }};
window.discordRequired = {{ .discordRequired }};
window.discordServerName = "{{ .discordServerName }}";
window.discordInviteLink = {{ .discordInviteLink }};
window.discordSendPINMessage = "{{ .discordSendPINMessage }}";
window.matrixEnabled = {{ .matrixEnabled }};
window.matrixRequired = {{ .matrixRequired }};
window.matrixUserID = "{{ .matrixUser }}";
window.validationStrings = JSON.parse({{ .validationStrings }});
</script>
{{ template "header.html" . }}
<title>{{ .strings.myAccount }}</title>
</head>
<body class="max-w-full overflow-x-hidden section">
<div id="modal-email" class="modal">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3">
<div class="content">
<span class="heading mb-4 my-2"></span>
<label class="label supra row m-1" for="modal-email-input">{{ .strings.emailAddress }}</label>
<div class="row">
<input type="email" class="col sm field ~neutral @low input" id="modal-email-input" placeholder="{{ .strings.emailAddress }}">
</div>
<button class="button ~urge @low supra full-width center lg my-2 modal-submit">{{ .strings.submit }}</button>
</div>
<div class="confirmation-required unfocused">
<span class="heading mb-4">{{ .strings.confirmationRequired }} <span class="modal-close">&times;</span></span>
<p class="content mb-4">{{ .strings.confirmationRequiredMessage }}</p>
</div>
</div>
</div>
{{ if .pwrEnabled }}
<div id="modal-pwr" class="modal">
<div class="card relative mx-auto my-[10%] w-4/5 lg:w-1/3 ~neutral @low">
<span class="heading">{{ .strings.resetPassword }}</span>
<p class="content my-2">
{{ if .linkResetEnabled }}
{{ .strings.resetPasswordThroughLink }}
{{ else }}
{{ .strings.resetPasswordThroughJellyfin }}
{{ end }}
</p>
<div class="row">
<input type="text" class="col sm field ~neutral @low input" id="pwr-address" placeholder="username | example@example.com | user#1234 | @user:host | @username">
</div>
{{ if .linkResetEnabled }}
<span class="button ~info @low full-width center mt-4" id="pwr-submit">
{{ .strings.submit }}
</span>
{{ else }}
<a class="button ~info @low full-width center mt-4" href="{{ .jfLink }}" target="_blank">{{ .strings.continue }}</a>
{{ end }}
</div>
</div>
{{ end }}
{{ template "login-modal.html" . }}
{{ template "account-linking.html" . }}
<div id="notification-box"></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>
</span>
<span class="button ~warning" alt="{{ .strings.theme }}" id="button-theme"><i class="ri-sun-line"></i></span>
<span class="button ~critical @low mb-4 unfocused" id="logout-button">{{ .strings.logout }}</span>
</div>
<div class="top-4 right-4 absolute">
<a class="button ~info unfocused" href="/" id="admin-back-button"><i class="ri-arrow-left-fill mr-2"></i>{{ .strings.admin }}</a>
</div>
<div class="page-container unfocused">
<div class="card @low dark:~d_neutral mb-4" id="card-user">
<span class="heading mb-2"></span>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
{{ if index . "PageMessageEnabled" }}
{{ if .PageMessageEnabled }}
<div class="card @low dark:~d_neutral content" id="card-message">
{{ .PageMessageContent }}
</div>
{{ end }}
{{ end }}
<div class="card @low dark:~d_neutral flex-col" id="card-contact">
<span class="heading mb-2">{{ .strings.contactMethods }}</span>
<div class="content flex justify-between flex-col h-100"></div>
</div>
<div>
<div class="card @low dark:~d_neutral content" id="card-password">
<span class="heading row mb-2">{{ .strings.changePassword }}</span>
<div class="">
<div class="my-2">
<span class="label supra row">{{ .strings.passwordRequirementsHeader }}</span>
<ul>
{{ range $key, $value := .requirements }}
<li class="" id="requirement-{{ $key }}" min="{{ $value }}">
<span class="badge lg ~positive requirement-valid"></span> <span class="content requirement-content"></span>
</li>
{{ end }}
</ul>
</div>
<div class="my-2">
<label class="label supra" for="user-old-password">{{ .strings.oldPassword }}</label>
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-old-password" aria-label="{{ .strings.oldPassword }}">
<label class="label supra" for="user-new-password">{{ .strings.newPassword }}</label>
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-new-password" aria-label="{{ .strings.newPassword }}">
<label class="label supra" for="user-reenter-password">{{ .strings.reEnterPassword }}</label>
<input type="password" class="input ~neutral @low mt-2 mb-4" placeholder="{{ .strings.password }}" id="user-reenter-new-password" aria-label="{{ .strings.reEnterPassword }}">
<span class="button ~info @low full-width center mt-4" id="user-password-submit">
{{ .strings.changePassword }}
</span>
</div>
</div>
</div>
</div>
<div>
<div class="card @low dark:~d_neutral unfocused" id="card-status">
<span class="heading mb-2">{{ .strings.expiry }}</span>
<aside class="aside ~warning user-expiry my-4"></aside>
<div class="user-expiry-countdown"></div>
</div>
</div>
</div>
</div>
<script src="{{ .urlBase }}/js/user.js" type="module"></script>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 1.9 MiB

BIN
images/discord/1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
images/discord/2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/discord/3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
images/discord/4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
images/discord/5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

BIN
images/discord/6.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

BIN
images/discord/7.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

BIN
images/discord/8.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 83 KiB

BIN
images/matrix/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

BIN
images/matrix/2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

BIN
images/matrix/3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
images/matrix/4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/tg-settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
images/tg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

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

42
internal.go Normal file
View File

@@ -0,0 +1,42 @@
// +build !external
package main
import (
"embed"
"io/fs"
"log"
)
const binaryType = "internal"
//go:embed data data/html data/web data/web/css data/web/js
var loFS embed.FS
//go:embed lang/common lang/admin lang/email lang/form lang/setup lang/pwreset lang/telegram
var laFS embed.FS
var langFS rewriteFS
var localFS rewriteFS
type rewriteFS struct {
fs embed.FS
prefix string
}
func (l rewriteFS) Open(name string) (fs.File, error) { return l.fs.Open(l.prefix + name) }
func (l rewriteFS) ReadDir(name string) ([]fs.DirEntry, error) { return l.fs.ReadDir(l.prefix + name) }
func (l rewriteFS) ReadFile(name string) ([]byte, error) { return l.fs.ReadFile(l.prefix + name) }
func FSJoin(elem ...string) string {
out := ""
for _, v := range elem {
out += v + "/"
}
return out[:len(out)-1]
}
func loadFilesystems() {
langFS = rewriteFS{laFS, "lang/"}
localFS = rewriteFS{loFS, "data/"}
log.Println("Using internal storage")
}

8
jfa-go.service Normal file
View File

@@ -0,0 +1,8 @@
[Unit]
Description=An account management system for Jellyfin.
[Service]
ExecStart={executable}
[Install]
WantedBy=default.target

201
lang.go
View File

@@ -1,11 +1,9 @@
package main
import (
"strings"
)
type langMeta struct {
Name string `json:"name"`
// Language to fall back on if strings are missing. Defaults to en-us.
Fallback string `json:"fallback,omitempty"`
}
type quantityString struct {
@@ -15,15 +13,23 @@ type quantityString struct {
type adminLangs map[string]adminLang
func (ls *adminLangs) getOptions(chosen string) (string, []string) {
opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
func (ls *adminLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for _, lang := range *ls {
opts[i] = lang.Meta.Name
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return chosenLang, opts
return opts
}
type commonLangs map[string]commonLang
type commonLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
Notifications langSection `json:"notifications"`
QuantityStrings map[string]quantityString `json:"quantityStrings"`
}
type adminLang struct {
@@ -34,56 +40,181 @@ type adminLang struct {
JSON string
}
type formLangs map[string]formLang
type userLangs map[string]userLang
func (ls *formLangs) getOptions(chosen string) (string, []string) {
opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
func (ls *userLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for _, lang := range *ls {
opts[i] = lang.Meta.Name
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return chosenLang, opts
return opts
}
type formLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
type userLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
Notifications langSection `json:"notifications"`
notificationsJSON string
ValidationStrings map[string]quantityString `json:"validationStrings"`
validationStringsJSON string
QuantityStrings map[string]quantityString `json:"quantityStrings"`
JSON string
}
type pwrLangs map[string]pwrLang
func (ls *pwrLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
}
type pwrLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
}
type emailLangs map[string]emailLang
func (ls *emailLangs) getOptions(chosen string) (string, []string) {
opts := make([]string, len(*ls))
chosenLang := (*ls)[chosen].Meta.Name
func (ls *emailLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for _, lang := range *ls {
opts[i] = lang.Meta.Name
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return chosenLang, opts
return opts
}
type emailLang struct {
Meta langMeta `json:"meta"`
UserCreated langSection `json:"userCreated"`
InviteExpiry langSection `json:"inviteExpiry"`
PasswordReset langSection `json:"passwordReset"`
UserDeleted langSection `json:"userDeleted"`
InviteEmail langSection `json:"inviteEmail"`
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
UserCreated langSection `json:"userCreated"`
InviteExpiry langSection `json:"inviteExpiry"`
PasswordReset langSection `json:"passwordReset"`
UserDeleted langSection `json:"userDeleted"`
UserDisabled langSection `json:"userDisabled"`
UserEnabled langSection `json:"userEnabled"`
InviteEmail langSection `json:"inviteEmail"`
WelcomeEmail langSection `json:"welcomeEmail"`
EmailConfirmation langSection `json:"emailConfirmation"`
UserExpired langSection `json:"userExpired"`
}
type setupLangs map[string]setupLang
type setupLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
StartPage langSection `json:"startPage"`
EndPage langSection `json:"endPage"`
General langSection `json:"general"`
Updates langSection `json:"updates"`
Language langSection `json:"language"`
Login langSection `json:"login"`
JellyfinEmby langSection `json:"jellyfinEmby"`
Ombi langSection `json:"ombi"`
Email langSection `json:"email"`
Messages langSection `json:"messages"`
Notifications langSection `json:"notifications"`
WelcomeEmails langSection `json:"welcomeEmails"`
PasswordResets langSection `json:"passwordResets"`
InviteEmails langSection `json:"inviteEmails"`
PasswordValidation langSection `json:"passwordValidation"`
HelpMessages langSection `json:"helpMessages"`
JSON string
}
func (ls *setupLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ls))
i := 0
for key, lang := range *ls {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
}
type telegramLangs map[string]telegramLang
type telegramLang struct {
Meta langMeta `json:"meta"`
Strings langSection `json:"strings"`
}
func (ts *telegramLangs) getOptions() [][2]string {
opts := make([][2]string, len(*ts))
i := 0
for key, lang := range *ts {
opts[i] = [2]string{key, lang.Meta.Name}
i++
}
return opts
}
type langSection map[string]string
type tmpl map[string]string
func templateString(text string, vals tmpl) string {
start, previousEnd := -1, -1
out := ""
for i := range text {
if text[i] == '{' {
start = i
continue
}
if start != -1 && text[i] == '}' {
varName := text[start+1 : i]
val, ok := vals[varName]
if !ok {
start = -1
continue
}
out += text[previousEnd+1:start] + val
previousEnd = i
start = -1
}
}
if previousEnd != len(text)-1 {
out += text[previousEnd+1:]
}
return out
}
func (el langSection) template(field string, vals tmpl) string {
text := el.get(field)
return templateString(text, vals)
}
func (el langSection) format(field string, vals ...string) string {
text := el.get(field)
for _, val := range vals {
text = strings.Replace(text, "{n}", val, 1)
start, previous := -1, -3
out := ""
val := 0
for i := range text {
if i == len(text)-2 { // Check if there's even enough space for a {n}
break
}
if text[i:i+3] == "{n}" {
start = i
out += text[previous+3:start] + vals[val]
previous = start
val++
if val == len(vals) {
break
}
}
}
return text
if previous+2 != len(text)-1 {
out += text[previous+3:]
}
return out
}
func (el langSection) get(field string) string {

204
lang/admin/da-dk.json Normal file
View File

@@ -0,0 +1,204 @@
{
"meta": {
"name": "Dansk (DK)"
},
"strings": {
"invites": "Invitationer",
"accounts": "Konti",
"settings": "Indstillinger",
"inviteMonths": "Måneder",
"inviteDays": "Dage",
"inviteHours": "Timer",
"inviteMinutes": "Minutter",
"inviteNumberOfUses": "Antal anvendelser",
"inviteDuration": "Invitations varighed",
"warning": "Advarsel",
"inviteInfiniteUsesWarning": "invitationer med uendelig brug kan blive misbrugt",
"inviteSendToEmail": "Send til",
"create": "Opret",
"apply": "Anvend",
"select": "Vælg",
"name": "Navn",
"date": "Dato",
"updates": "Opdateringer",
"update": "Opdatering",
"download": "Hent",
"search": "Søg",
"advancedSettings": "Avanceret Indstillinger",
"lastActiveTime": "Sidst Aktiv",
"from": "Fra",
"user": "Bruger",
"userExpiry": "Brugerens Udløb",
"userExpiryDescription": "En specificeret tid efter hver tilmelding, sletter/deaktiverer jfa-go kontoen. Du kan ændre denne adfærd i indstillingerne.",
"aboutProgram": "Om",
"version": "Version",
"commitNoun": "Commit",
"newUser": "Ny Bruger",
"profile": "Profil",
"unknown": "Ukendt",
"label": "Etiket",
"announce": "Annoncere",
"subject": "Emne",
"message": "Meddelelse",
"variables": "Variabler",
"conditionals": "Betingelser",
"preview": "Eksempel",
"reset": "Nulstil",
"donate": "Doner",
"contactThrough": "Kontakt gennem:",
"extendExpiry": "Forlæng udløb",
"customizeMessages": "Tilpas Meddelelser",
"customizeMessagesDescription": "Hvis du ikke vil bruge jfa-go's meddelelses skabeloner, kan du oprette din egen ved hjælp af Markdown.",
"markdownSupported": "Markdown understøttes.",
"modifySettings": "Rediger indstillinger",
"modifySettingsDescription": "Anvend indstillinger fra en eksisterende profil, eller hent dem direkte fra en bruger.",
"applyHomescreenLayout": "Anvend startskærmens layout",
"sendDeleteNotificationEmail": "Send notifikations meddelelse",
"sendDeleteNotifiationExample": "Din konto er blevet slettet.",
"settingsRestart": "Genstart",
"settingsRestarting": "Genstarter…",
"settingsRestartRequired": "Genstart nødvendig",
"settingsRestartRequiredDescription": "En genstart er nødvendig for at anvende nogle indstillinger du har ændret. Genstart nu eller senere?",
"settingsApplyRestartLater": "Anvend, genstart senere",
"settingsApplyRestartNow": "Anvend & genstart",
"settingsApplied": "Indstillingerne anvendt.",
"settingsRefreshPage": "Opdater siden om få sekunder.",
"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 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",
"userProfilesLibraries": "Biblioteker",
"addProfile": "Tilføj Profil",
"addProfileDescription": "Opret en Jellyfin bruger og konfigurer den, vælg den derefter nedenfor. Når denne profil anvendes på en invitation, oprettes nye brugere med indstillingerne.",
"addProfileNameOf": "Profil Navn",
"addProfileStoreHomescreenLayout": "Gem startskærmens layout",
"inviteNoUsersCreated": "Ingen endnu!",
"inviteUsersCreated": "Oprettet brugere",
"inviteNoProfile": "Ingen Profil",
"inviteDateCreated": "Oprettet",
"inviteRemainingUses": "Resterende anvendelser",
"inviteNoInvites": "Ingen",
"inviteExpiresInTime": "Udløber om {n}",
"notifyEvent": "Meddel den:",
"notifyInviteExpiry": "Ved udløb",
"notifyUserCreation": "Ved oprettelse af brugere",
"sendPIN": "Bed brugeren om at sende pinkoden nedenfor til botten.",
"searchDiscordUser": "Begynd at skrive Discord brugernavnet for at finde brugeren.",
"findDiscordUser": "Find Discord bruger",
"linkMatrixDescription": "Indtast brugernavnet og adgangskoden til den bruger der skal bruges som en bot. Når indsendt, genstarter appen.",
"matrixHomeServer": "Hjemme server adresse",
"saveAsTemplate": "Gem som skabelon",
"templates": "Skabeloner",
"deleteTemplate": "Slet 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}.",
"userCreated": "Bruger {n} oprettet.",
"createProfile": "Oprettede profil {n}.",
"saveSettings": "Indstillingerne blev gemt",
"saveEmail": "E-mail gemt.",
"sentAnnouncement": "Meddelelse sendt.",
"setOmbiDefaults": "Ombi standarder gemt.",
"updateApplied": "Opdatering anvendt, genstart.",
"updateAppliedRefresh": "Opdatering anvendt, genindlæs venligst siden.",
"telegramVerified": "Telegram konto verificeret.",
"accountConnected": "Konto tilsluttet.",
"errorSettingsAppliedNoHomescreenLayout": "Indstillingerne blev anvendt, men anvendelse af startskærmens layout mislykkedes muligvis.",
"errorHomescreenAppliedNoSettings": "Startskærmens layout blev anvendt, men anvendelsen af indstillingerne mislykkedes muligvis.",
"errorSettingsFailed": "Ansøgningen mislykkedes.",
"errorSaveEmail": "Kunne ikke gemme e-mail.",
"errorBlankFields": "Felter blev efterladt tomme",
"errorDeleteProfile": "Kunne ikke slette profilen {n}",
"errorLoadProfiles": "Profiler kunne ikke indlæses.",
"errorCreateProfile": "Kunne ikke oprette profilen {n}",
"errorSetDefaultProfile": "Standard profilen kunne ikke indstilles.",
"errorLoadUsers": "Kunne ikke indlæse brugere.",
"errorLoadSettings": "Indstillingerne kunne ikke indlæses.",
"errorSetOmbiDefaults": "Ombi standarderne kunne ikke gemmes.",
"errorLoadOmbiUsers": "Kunne ikke indlæse ombi brugere.",
"errorChangedEmailAddress": "Kunne ikke ændre e-mail adressen på {n}.",
"errorFailureCheckLogs": "Mislykkedes (tjek konsol/logfiler)",
"errorPartialFailureCheckLogs": "Delvis fejl (tjek konsol/logfiler)",
"errorUserCreated": "Kunne ikke oprette bruger {n}.",
"errorSendWelcomeEmail": "Kunne ikke sende velkomst meddelelse (tjek konsol/logfiler",
"errorApplyUpdate": "Kunne ikke anvende opdateringen, prøv manuelt.",
"errorCheckUpdate": "Kunne ikke kontrollere for opdatering.",
"updateAvailable": "En ny opdatering er tilgængelig, tjek indstillingerne.",
"noUpdatesAvailable": "Ingen nye opdateringer tilgængelige.",
"savedAnnouncement": "Meddelelse gemt.",
"setOmbiProfile": "Gemt i ombi profilen.",
"errorSetOmbiProfile": "Ombi profilen kunne ikke gemmes."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Rediger indstillinger for {n} bruger",
"plural": "Rediger indstillinger for {n} brugere"
},
"deleteNUsers": {
"singular": "Slet {n} bruger",
"plural": "Slet {n} brugere"
},
"disableUsers": {
"singular": "Deaktiver {n} bruger",
"plural": "Deaktiver {n} brugere"
},
"reEnableUsers": {
"singular": "Genaktiver {n} bruger",
"plural": "Genaktiver {n} brugere"
},
"addUser": {
"singular": "Tilføj bruger",
"plural": "Tilføj brugere"
},
"deleteUser": {
"singular": "Slet bruger",
"plural": "Slet brugere"
},
"deletedUser": {
"singular": "Slettede {n} bruger.",
"plural": "Slettede {n} brugere."
},
"disabledUser": {
"singular": "Deaktiveret {n} bruger.",
"plural": "Deaktiverede {n} brugere."
},
"enabledUser": {
"singular": "Aktiveret {n} bruger.",
"plural": "Aktiveret {n} brugere."
},
"announceTo": {
"singular": "Annoncer til {n} bruger",
"plural": "Annoncer til {n} brugere"
},
"appliedSettings": {
"singular": "Anvendte indstillinger til {n} bruger.",
"plural": "Anvendte indstillinger til {n} brugere."
},
"extendExpiry": {
"singular": "Forlæng udløbet for {n} bruger",
"plural": "Forlæng udløbet for {n} brugere"
},
"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

@@ -6,7 +6,6 @@
"invites": "Invites",
"accounts": "Konten",
"settings": "Einstellungen",
"theme": "Thema",
"inviteDays": "Tage",
"inviteHours": "Stunden",
"inviteMinutes": "Minuten",
@@ -14,17 +13,10 @@
"warning": "Warnung",
"inviteInfiniteUsesWarning": "Invites mit unendlich vielen Verwendungen können missbräuchlich verwendet werden",
"inviteSendToEmail": "Senden an",
"login": "Anmelden",
"logout": "Abmelden",
"create": "Erstellen",
"apply": "Anwenden",
"delete": "Löschen",
"submit": "Absenden",
"name": "Name",
"date": "Datum",
"username": "Benutzername",
"password": "Passwort",
"emailAddress": "E-Mail-Adresse",
"lastActiveTime": "Zuletzt aktiv",
"from": "Von",
"user": "Benutzer",
@@ -33,24 +25,22 @@
"commitNoun": "Commit",
"newUser": "Neuer Benutzer",
"profile": "Profil",
"success": "Erfolg",
"error": "Fehler",
"unknown": "Unbekannt",
"modifySettings": "Einstellungen ändern",
"modifySettingsDescription": "Wende Einstellungen von einem bestehenden Profil an, oder beziehe sie direkt von einem Benutzer.",
"applyHomescreenLayout": "Startbildschirmlayout anwenden",
"sendDeleteNotificationEmail": "Benachrichtigungs-E-Mail senden",
"sendDeleteNotificationEmail": "Benachrichtigung senden",
"sendDeleteNotifiationExample": "Dein Konto wurde gelöscht.",
"settingsRestartRequired": "Neustart erforderlich",
"settingsRestartRequiredDescription": "Ein Neustart ist notwendig, um einige Einstellungen anzuwenden, die du geändert hast. Jetzt oder später neu starten?",
"settingsApplyRestartLater": "Anwenden, später neu starten",
"settingsApplyRestartNow": "Anwenden & neu starten",
"settingsApplied": "Einstellungen angewendet.",
"settingsRefreshPage": "Aktualisiere die Seite in ein paar Sekunden",
"settingsRefreshPage": "Aktualisiere die Seite in ein paar Sekunden.",
"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",
@@ -62,14 +52,58 @@
"inviteNoUsersCreated": "Noch keine!",
"inviteUsersCreated": "Erstellte Benutzer",
"inviteNoProfile": "Kein Profil",
"copy": "Kopieren",
"inviteDateCreated": "Erstellt",
"inviteRemainingUses": "Verbleibende Verwendungen",
"inviteNoInvites": "Keine",
"inviteExpiresInTime": "Läuft in {n} ab",
"notifyEvent": "Benachrichtigen bei:",
"notifyInviteExpiry": "Bei Ablauf",
"notifyUserCreation": "Bei Benutzererstellung"
"notifyUserCreation": "Bei Benutzererstellung",
"label": "Label",
"settingsRestarting": "Neustart…",
"settingsRestart": "Neustart",
"variables": "Variablen",
"preview": "Vorschau",
"reset": "Zurücksetzen",
"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",
"message": "Nachricht",
"markdownSupported": "Markdown wird unterstützt.",
"advancedSettings": "Erweiterte Einstellungen",
"search": "Suchen",
"userExpiry": "Benutzer Ablaufdatum",
"inviteDuration": "Invite Dauer",
"userExpiryDescription": "Eine bestimmte Zeit nach der Anmeldung wird jfa-go das Konto löschen/deaktivieren. Du kannst dieses Verhalten in den Einstellungen ändern.",
"download": "Herunterladen",
"update": "Aktualisieren",
"updates": "Aktualisierungen",
"extendExpiry": "Ablaufdatum verlängern",
"donate": "Spenden",
"conditionals": "Bedingungen",
"contactThrough": "Kontakt über:",
"sendPIN": "Bitte den Benutzer, die unten stehende PIN an den Bot zu senden.",
"inviteMonths": "Monate",
"select": "Auswählen",
"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",
"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.",
@@ -77,26 +111,37 @@
"createProfile": "Profil {n} erstellt.",
"saveSettings": "Einstellungen wurden gespeichert",
"setOmbiDefaults": "Ombi-Standardeinstellungen gespeichert.",
"errorConnection": "Konnte keine Verbindung zu jfa-go herstellen.",
"error401Unauthorized": "Unberechtigt. Versuch, die Seite zu aktualisieren.",
"errorSettingsAppliedNoHomescreenLayout": "Einstellungen wurden angewendet, aber die Anwendung des Startbildschirmlayouts ist möglicherweise fehlgeschlagen.",
"errorHomescreenAppliedNoSettings": "Startbildschirmlayout wurde angewendet, aber die Anwendung der Einstellungen ist möglicherweise fehlgeschlagen.",
"errorSettingsFailed": "Anwendung ist fehlgeschlagen.",
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",
"errorUnknown": "Unbekannter Fehler.",
"errorBlankFields": "Felder wurden nicht ausgefüllt",
"errorDeleteProfile": "Fehler beim Löschen des Profils {n}",
"errorLoadProfiles": "Fehler beim Laden der Profile.",
"errorCreateProfile": "Fehler beim Erstellen des Profils {n}",
"errorSetDefaultProfile": "Fehler beim Setzen des Standardprofils.",
"errorLoadUsers": "Fehler beim Laden der Benutzer.",
"errorSaveSettings": "Einstellungen konnten nicht gespeichert werden.",
"errorLoadSettings": "Fehler beim Laden der Einstellungen.",
"errorSetOmbiDefaults": "Fehler beim Speichern der Ombi-Standardeinstellungen.",
"errorLoadOmbiUsers": "Fehler beim Laden der Ombi-Benutzer.",
"errorChangedEmailAddress": "E-Mail-Adresse von {n} konnte nicht geändert werden.",
"errorFailureCheckLogs": "Fehlgeschlagen (überprüfe die Konsole/Logs)",
"errorPartialFailureCheckLogs": "Teilweiser Fehlschlag (überprüfe die Konsole/Logs)"
"errorPartialFailureCheckLogs": "Teilweiser Fehlschlag (überprüfe die Konsole/Logs)",
"errorUserCreated": "Fehler beim Erstellen des Benutzers {n}.",
"errorSendWelcomeEmail": "Fehler beim Senden der Willkommensnachricht (überprüfe die Konsole/Logs)",
"saveEmail": "E-Mail gespeichert.",
"errorSaveEmail": "Fehler beim Speichern der E-Mail.",
"sentAnnouncement": "Ankündigung gesendet.",
"updateApplied": "Aktualisierung angewendet, bitte neu starten.",
"errorApplyUpdate": "Fehler beim Anwenden der Aktualisierung, versuche es manuell.",
"errorCheckUpdate": "Fehler beim Suchen nach Aktualisierungen.",
"updateAvailable": "Eine neue Aktualisierung ist verfügbar, überprüfe die Einstellungen.",
"noUpdatesAvailable": "Keinen neuen Aktualisierungen verfügbar.",
"updateAppliedRefresh": "Update angewendet, bitte aktualisieren.",
"telegramVerified": "Telegram-Konto verifiziert.",
"accountConnected": "Konto verbunden.",
"savedAnnouncement": "Ankündigung gespeichert.",
"errorSetOmbiProfile": "Ombi-Profil konnte nicht gespeichert werden.",
"setOmbiProfile": "Ombi-Profil gespeichert."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -122,6 +167,38 @@
"appliedSettings": {
"singular": "Einstellungen auf {n} Benutzer angewendet.",
"plural": "Einstellungen auf {n} Benutzer angewendet."
},
"announceTo": {
"singular": "{n} Benutzer mitteilen",
"plural": "{n} Benutzern mitteilen"
},
"extendExpiry": {
"singular": "Ablaufdatum für {n} Benutzer verlängern",
"plural": "Ablaufdatum für {n} Benutzer verlängern"
},
"extendedExpiry": {
"singular": "Ablaufdatum für {n} Benutzer verlängern.",
"plural": "Ablaufdatum für {n} Benutzer verlängern."
},
"disabledUser": {
"plural": "Benutzer {n} Deaktiviert.",
"singular": "Benutzer {n} Deaktiviert."
},
"enabledUser": {
"singular": "Benutzer {n} Aktiviert.",
"plural": "Benutzer {n} Aktiviert."
},
"disableUsers": {
"singular": "Benutzer {n} deaktivieren",
"plural": "Deaktiviere {n} Benutzer"
},
"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"
}
}
}
}

171
lang/admin/el-gr.json Normal file
View File

@@ -0,0 +1,171 @@
{
"meta": {
"name": "Ελληνικά (GR)"
},
"strings": {
"invites": "Προσκλήσεις",
"accounts": "Λογαριασμοί",
"settings": "Ρυθμίσεις",
"inviteDays": "Ημέρες",
"inviteHours": "Ώρες",
"inviteMinutes": "Λεπτά",
"inviteNumberOfUses": "Αριθμός χρήσεων",
"warning": "Προσοχή",
"inviteInfiniteUsesWarning": "μπορεί να γίνει κατάχρηση των προσκλήσεων με άπειρες χρήσεις",
"inviteSendToEmail": "Αποστολή σε",
"create": "Δημιουργία",
"apply": "Εφαρμογή",
"name": "Όνομα",
"date": "Ημερομηνία",
"lastActiveTime": "Τελευταία Ενεργός",
"from": "Απο",
"user": "Χρήστης",
"aboutProgram": "Σχετικά",
"version": "Έκδοση",
"commitNoun": "Καταχώρηση",
"newUser": "Νέος Χρήστης",
"profile": "Προφίλ",
"unknown": "Άγνωστο",
"label": "Ετικέτα",
"modifySettings": "Επεξεργασία Ρυθμίσεων",
"modifySettingsDescription": "Εφαρμογή ρυθμίσεων απο υπάρχον προφίλ ή απευθείας απο χρήστη.",
"applyHomescreenLayout": "Εφαρμογή δομής αρχικής οθόνης",
"sendDeleteNotificationEmail": "Αποστολή ενημερωτικού email",
"sendDeleteNotifiationExample": "Ο λογαριασμός σας έχει διαγραφεί.",
"settingsRestart": "Επανεκίνηση",
"settingsRestarting": "Επανεκινεί…",
"settingsRestartRequired": "Απαιτείται επανεκκίνηση",
"settingsRestartRequiredDescription": "Απαιτείται επανεκκίνηση για να εφαρμοστούν κάποιες απο τις ρυθμίσεις που αλλάξατε. Επανεκκίνηση τώρα ή αργότερα?",
"settingsApplyRestartLater": "Εφαρμογή, επανεκκίνηση αργότερα",
"settingsApplyRestartNow": "Εφαρμογή και επανεκκίνηση",
"settingsApplied": "Οι ρυθμίσεις εφαρμόστηκαν.",
"settingsRefreshPage": "Επαναφορτόστε την σελίδα σε μερικά δεύτερα.",
"settingsRequiredOrRestartMessage": "Σημείωση: {n} δηλώνει υποχρεωτικό πεδίο, {n} δηλώνει αλλαγές που απαιτούν επανεκκίνηση.",
"settingsSave": "Αποθήκευση",
"ombiUserDefaults": "Προεπιλογές χρήστη Ombi",
"ombiUserDefaultsDescription": "Δημιουργήστε έναν χρήστη Ombi και ρυθμίστε τον, μετά επιλέξτε τον απο κάτω. Οι ρυθμίσεις/άδειες θα αποθηκευτούν και εφαρμοστούν σε νέους χρήστες Ombi που έχουν δημιουργηθεί απο το jfa-go",
"userProfiles": "Προφιλ Χρήστη",
"userProfilesDescription": "Τα προφίλ εφαρμόζονται στους χρήστες όταν δημιουργούν λογαριασμό. Το προφίλ περιέχει δικαιώματα στις βιβλιοθήκες και δομή αρχικής οθόνης.",
"userProfilesIsDefault": "Προκαθορισμένα",
"userProfilesLibraries": "Βιβλιοθήκες",
"addProfile": "Προσθήκη Προφίλ",
"addProfileDescription": "Δημιουργήστε χρήστη Jellyfin και ρυθμίστε τον, μετά επιλέξτε τον παρακάτω. Όταν αυτό το προφίλ επιλέγεται σε μία πρόσκληση, τότε οι νέοι χρήστες θα δημιουργηθούν με αυτές τις ρυθμίσεις.",
"addProfileNameOf": "Όνομα Προφίλ",
"addProfileStoreHomescreenLayout": "Αποθήκευση αρχικής οθόνης",
"inviteNoUsersCreated": "Τίποτα ακόμα!",
"inviteUsersCreated": "Δημιουργηθέντες χρήστες",
"inviteNoProfile": "Κανένα Προφίλ",
"inviteDateCreated": "Δημιουργηθέντα",
"inviteRemainingUses": "Εναπομείναντες χρήσεις",
"inviteNoInvites": "Καμία",
"inviteExpiresInTime": "Λήγει σε {n}",
"notifyEvent": "Ενημέρωση όταν:",
"notifyInviteExpiry": "Στην λήξη",
"notifyUserCreation": "Στην δημιουργία χρήστη",
"variables": "Μεταβλητές",
"preview": "Προεπισκόπηση",
"reset": "Επαναφορά",
"customizeMessages": "Παραμετροποίηση Emails",
"advancedSettings": "Προχωρημένες Ρυθμίσεις",
"customizeMessagesDescription": "Αν δεν θέλετε να ζρησιμοποιήσετε τα πρότυπα email του jfa-go, μπορείτε να δημιουργήσετε τα δικά σας με χρήση Markdown.",
"updates": "Ενημερώσεις",
"update": "Ενημέρωση",
"download": "Λήψη",
"search": "Αναζήτηση",
"inviteDuration": "Διάρκεια Πρόσκλησης",
"userExpiry": "Λήξη Χρήστη",
"userExpiryDescription": "Μετά απο ένα καθορισμένο χρόνο μετά απο κάθε εγγραφή, το jfa-go θα διαγράφει/απενεργοποιεί τον λογαριασμό. Μπορείτε να αλλάξετε αυτή την συμπεριφορά στις ρυθμίσεις.",
"announce": "Ανακοίνωση",
"subject": "Θέμα Email",
"message": "Μήνυμα",
"extendExpiry": "Παράταση λήξης",
"markdownSupported": "Το Markdown υποστυρίζεται.",
"inviteMonths": "Μήνες"
},
"notifications": {
"changedEmailAddress": "Αλλαγή {n} διεύθυνσεων email.",
"userCreated": "Δημιουργήθηκε ο {n} χρήστης.",
"createProfile": "Δημιουργήθηκε το {n} προφίλ.",
"saveSettings": "Οι ρυθμίσεις αποθηκεύτηκαν",
"setOmbiDefaults": "Αποθηκεύτηκαν οι προκαθορισμένες ρυθμίσεις του ombi.",
"errorSettingsAppliedNoHomescreenLayout": "Οι ρυθμίσεις αποθηκεύτηκαν, αλλά η καταχώρηση δομής αρχικής οθόνης ίσως απέτυχε.",
"errorHomescreenAppliedNoSettings": "Η δομή αρχικής οθόνης εφαρμόστηκε, αλλά οι ρυθμίσεις ίσως απέτυχαν.",
"errorSettingsFailed": "Η εφαρμογή απέτυχε.",
"errorBlankFields": "Τα πεφία ήταν κενά",
"errorDeleteProfile": "Αποτυχία διαγραφής του προφίλ {n}",
"errorLoadProfiles": "Αποτυχία φόρτωσης των προφίλ.",
"errorCreateProfile": "Αποτυχία δημιουργίας του προφίλ {n}",
"errorSetDefaultProfile": "Αποτυχία ορισμού του προκαθορισμένου προφίλ.",
"errorLoadUsers": "Αποτυχία φόρτωσης χρηστών.",
"errorLoadSettings": "Αποτυχία φόρτωσης ρυθμίσεων.",
"errorSetOmbiDefaults": "Αποτυχία αποθήκευσης προκαθορισμένων ρυθμίσεων για το Ombi.",
"errorLoadOmbiUsers": "Αποτυχία φόρτωσης χρηστών Ombi.",
"errorChangedEmailAddress": "Αποτυχία αλλαγής email του {n}.",
"errorFailureCheckLogs": "Αποτυχία (ελέγξτε κονσόλα/καταγραφές)",
"errorPartialFailureCheckLogs": "Μερική αποτυχία (ελέγξτε κονσόλα/καταγραφές)",
"errorUserCreated": "Αποτυχία δημιουργίας του χρήστη {n}.",
"errorSendWelcomeEmail": "Αποτυχία αποστολής email καλωσορίσματος (ελέγξτε κονσόλα/καταγραφές)",
"saveEmail": "Το email αποθηκεύτηκε.",
"sentAnnouncement": "Ανακοίνωση εστάλη.",
"updateApplied": "Η ενημέρωση εφαρμόστηκε, παρακαλώ επανεκκινήστε.",
"errorSaveEmail": "Αποτυχία αποθήκευσης του email.",
"errorApplyUpdate": "Αποτυχία εγκατάστασης ενημέρωσης, προσπαθήστε χειροκίνητα.",
"errorCheckUpdate": "Αποτυχία ελέγχου για ενημερώσεις.",
"updateAvailable": "Μια νέα ενημέρωση είναι διαθέσιμη, ελέγξτε τις ρυθμίσεις.",
"noUpdatesAvailable": "Δεν υπάρχουν διαθέσιμες ενημερώσεις."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Επεξεργασία Ρυθμίσεων για {n} χρήστη",
"plural": "Επεξεργασία Ρυθμίσεων για {n} χρήστες"
},
"deleteNUsers": {
"singular": "Διαγραφή {n} χρήστη",
"plural": "Διαγραφή {n} χρηστών"
},
"addUser": {
"singular": "Προσθήκη χρήστη",
"plural": "Προσθήκη χρηστών"
},
"deleteUser": {
"singular": "Διαγραφή Χρήστη",
"plural": "Διαγραφή Χρηστών"
},
"deletedUser": {
"singular": "Διαγράφη {n} χρήστη.",
"plural": "Διαγράφησαν {n} χρήστες."
},
"appliedSettings": {
"singular": "Εφαρμογή ρυθμίσεων σε {n} χρήστη.",
"plural": "Εφαρμογή ρυθμίσεων σε {n} χρήστες."
},
"announceTo": {
"singular": "Ανακοίνωση σε {n} χρήστη",
"plural": "Ανακοίνωση σε {n} χρήστες"
},
"extendExpiry": {
"plural": "Επέκταση λήξης σε {n} χρήστες",
"singular": "Επέκταση λήξης σε {n} χρήστη"
},
"extendedExpiry": {
"singular": "Εκτεταμένη λήξη για {n} χρήστη.",
"plural": "Εκτεταμένη λήξη για {n} χρήστες."
},
"disableUsers": {
"singular": "Απενεργοποίηση {n} χρήστη",
"plural": "Απενεργοποίηση {n} χρηστών"
},
"reEnableUsers": {
"singular": "Εκ νέου ενεργοποίηση {n} χρήστη",
"plural": "Εκ νέου ενεργοποίηση {n} χρηστών"
},
"disabledUser": {
"singular": "Απενεργοποιήθηκε {n} χρήστης.",
"plural": "Απενεργοποιήθηκαν {n} χρήστες."
},
"enabledUser": {
"singular": "Εργοποιήθηκε {n} χρήστης.",
"plural": "Εργοποιήθηκαν {n} χρήστες."
}
}
}

201
lang/admin/en-gb.json Normal file
View File

@@ -0,0 +1,201 @@
{
"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",
"apply": "Apply",
"updates": "Updates",
"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",
"extendExpiry": "Extend expiry",
"sendPWR": "Send Password Reset",
"inviteMonths": "Months",
"inviteDuration": "Invite Duration",
"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",
"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.",
"errorLoadSettings": "Failed to load settings.",
"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.",
"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.",
"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",
"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

@@ -6,101 +6,155 @@
"invites": "Invites",
"accounts": "Accounts",
"settings": "Settings",
"theme": "Theme",
"inviteMonths": "Months",
"inviteDays": "Days",
"inviteHours": "Hours",
"inviteMinutes": "Minutes",
"inviteNumberOfUses": "Number of uses",
"inviteDuration": "Invite Duration",
"warning": "Warning",
"inviteInfiniteUsesWarning": "invites with infinite uses can be used abusively",
"inviteSendToEmail": "Send to",
"login": "Login",
"logout": "Logout",
"create": "Create",
"apply": "Apply",
"delete": "Delete",
"submit": "Submit",
"select": "Select",
"name": "Name",
"date": "Date",
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"setExpiry": "Set expiry",
"updates": "Updates",
"update": "Update",
"download": "Download",
"search": "Search",
"advancedSettings": "Advanced Settings",
"lastActiveTime": "Last Active",
"from": "From",
"after": "After",
"before": "Before",
"user": "User",
"userExpiry": "User Expiry",
"userExpiryDescription": "A specified amount of time after each signup, jfa-go will delete/disable the account. You can change this behaviour in settings.",
"aboutProgram": "About",
"version": "Version",
"commitNoun": "Commit",
"newUser": "New User",
"profile": "Profile",
"success": "Success",
"error": "Error",
"unknown": "Unknown",
"label": "Label",
"logs": "Logs",
"announce": "Announce",
"templates": "Templates",
"subject": "Subject",
"message": "Message",
"variables": "Variables",
"conditionals": "Conditionals",
"preview": "Preview",
"reset": "Reset",
"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.",
"modifySettings": "Modify Settings",
"modifySettingsDescription": "Apply settings from an existing profile, or source them directly from a user.",
"applyHomescreenLayout": "Apply homescreen layout",
"sendDeleteNotificationEmail": "Send notification email",
"sendDeleteNotificationEmail": "Send notification message",
"sendDeleteNotifiationExample": "Your account has been deleted.",
"settingsRestart": "Restart",
"settingsRestarting": "Restarting…",
"settingsRestartRequired": "Restart needed",
"settingsRestartRequiredDescription": "A restart is necessary to apply some settings you changed. Restart now or later?",
"settingsApplyRestartLater": "Apply, restart later",
"settingsApplyRestartNow": "Apply & restart",
"settingsApplied": "Settings applied.",
"settingsRefreshPage": "Refresh the page in a few seconds",
"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.",
"userProfilesDescription": "Profiles are applied to users when they create an account. A profile includes library access rights and homescreen layout.",
"userProfilesIsDefault": "Default",
"userProfilesLibraries": "Libraries",
"addProfile": "Add Profile",
"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.",
"addProfileNameOf": "Profile Name",
"addProfileStoreHomescreenLayout": "Store homescreen layout",
"inviteNoUsersCreated": "None yet!",
"inviteUsersCreated": "Created users",
"inviteNoProfile": "No Profile",
"copy": "Copy",
"inviteDateCreated": "Created",
"inviteRemainingUses": "Remaining uses",
"inviteNoInvites": "None",
"inviteExpiresInTime": "Expires in {n}",
"notifyEvent": "Notify on:",
"notifyInviteExpiry": "On expiry",
"notifyUserCreation": "On user creation"
"notifyUserCreation": "On user creation",
"sendPIN": "Ask the user to send the PIN below to the bot.",
"searchDiscordUser": "Start typing the Discord username to find the user.",
"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",
"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",
"userPageLogin": "User Page: Login",
"userPagePage": "User Page: Page",
"buildTime": "Build Time",
"builtBy": "Built By"
},
"notifications": {
"changedEmailAddress": "Changed email address of {n}.",
"userCreated": "User {n} created.",
"createProfile": "Created profile {n}.",
"saveSettings": "Settings were saved",
"setOmbiDefaults": "Stored ombi defaults.",
"errorConnection": "Couldn't connect to jfa-go.",
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
"saveEmail": "Email saved.",
"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.",
"errorSettingsAppliedNoHomescreenLayout": "Settings were applied, but applying homescreen layout may have failed.",
"errorHomescreenAppliedNoSettings": "Homescreen layout was applied, but applying settings may have failed.",
"errorSettingsFailed": "Application failed.",
"errorLoginBlank": "The username and/or password were left blank.",
"errorUnknown": "Unknown error.",
"errorSaveEmail": "Failed to save email.",
"errorBlankFields": "Fields were left blank",
"errorDeleteProfile": "Failed to delete profile {n}",
"errorLoadProfiles": "Failed to load profiles.",
"errorCreateProfile": "Failed to create profile {n}",
"errorSetDefaultProfile": "Failed to set default profile.",
"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)",
"errorPartialFailureCheckLogs": "Partial failure (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.",
"updateAvailable": "A new update is available, check settings.",
"noUpdatesAvailable": "No new updates available."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Modify Settings for {n} user",
@@ -110,6 +164,14 @@
"singular": "Delete {n} user",
"plural": "Delete {n} users"
},
"disableUsers": {
"singular": "Disable {n} user",
"plural": "Disable {n} users"
},
"reEnableUsers": {
"singular": "Re-enable {n} user",
"plural": "Re-enable {n} users"
},
"addUser": {
"singular": "Add user",
"plural": "Add users"
@@ -122,9 +184,33 @@
"singular": "Deleted {n} user.",
"plural": "Deleted {n} users."
},
"disabledUser": {
"singular": "Disabled {n} user.",
"plural": "Disabled {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."
},
"extendExpiry": {
"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."
}
}
}

204
lang/admin/es-es.json Normal file
View File

@@ -0,0 +1,204 @@
{
"meta": {
"name": "Español (ES)"
},
"strings": {
"invites": "Invitaciones",
"accounts": "Cuentas",
"settings": "Ajustes",
"inviteMonths": "Meses",
"inviteDays": "Días",
"inviteHours": "Horas",
"inviteMinutes": "Minutos",
"inviteNumberOfUses": "Números de usos",
"inviteDuration": "Duración de invitación",
"warning": "Advertencia",
"inviteInfiniteUsesWarning": "Las invitaciones con usos infinitos se pueden usar de forma abusiva",
"inviteSendToEmail": "Enviar a",
"create": "Crear",
"apply": "Aplicar",
"name": "Nombre",
"date": "Fecha",
"updates": "Actualizaciones",
"update": "Actualizar",
"download": "Descargar",
"search": "Buscar",
"advancedSettings": "Ajustes avanzados",
"lastActiveTime": "Último activo",
"from": "De",
"user": "Usuario",
"userExpiry": "Caducidad del usuario",
"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": "Revisión",
"newUser": "Nuevo usuario",
"profile": "Perfil",
"unknown": "Desconocido",
"label": "Etiqueta",
"announce": "Anunciar",
"subject": "Asunto del email",
"message": "Mensaje",
"variables": "Variables",
"preview": "Vista previa",
"reset": "Reiniciar",
"extendExpiry": "Extender el vencimiento",
"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 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 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 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": "Por defecto",
"userProfilesLibraries": "Bibliotecas",
"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": "Guardar el diseño de la pantalla de inicio",
"inviteNoUsersCreated": "¡Ninguno todavía!",
"inviteUsersCreated": "Usuarios creados",
"inviteNoProfile": "Sin perfil",
"inviteDateCreated": "Creado",
"inviteRemainingUses": "Usos restantes",
"inviteNoInvites": "Ninguno",
"inviteExpiresInTime": "Caduca en {n}",
"notifyEvent": "Notificar en:",
"notifyInviteExpiry": "Al vencimiento",
"notifyUserCreation": "Sobre la creación de usuarios",
"conditionals": "Condicionales",
"donate": "Donar",
"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}.",
"userCreated": "Usuario {n} creado.",
"createProfile": "Perfil creado {n}.",
"saveSettings": "Se guardaron las configuraciones",
"saveEmail": "Correo electrónico guardado.",
"sentAnnouncement": "Anuncio enviado.",
"setOmbiDefaults": "Valores predeterminados de ombi almacenados.",
"updateApplied": "Actualización aplicada, por favor reinicie.",
"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 la aplicación de la configuración haya fallado.",
"errorSettingsFailed": "La aplicación falló.",
"errorSaveEmail": "No se pudo guardar el correo electrónico.",
"errorBlankFields": "Los campos se dejaron en blanco",
"errorDeleteProfile": "No se pudo borrar el perfil {n}",
"errorLoadProfiles": "No se pudieron cargar los perfiles.",
"errorCreateProfile": "No se pudo crear el perfil {n}",
"errorSetDefaultProfile": "No se pudo establecer el perfil predeterminado.",
"errorLoadUsers": "No se pudieron cargar los usuarios.",
"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.",
"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)",
"errorUserCreated": "No se pudo crear el usuario {n}.",
"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.",
"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": {
"singular": "Modificar la configuración de {n} usuario",
"plural": "Modificar la configuración de {n} usuarios"
},
"deleteNUsers": {
"singular": "Eliminar {n} usuario",
"plural": "Eliminar {n} usuarios"
},
"disableUsers": {
"singular": "Desactivar {n} usuario",
"plural": "Desactivar {n} usuarios"
},
"reEnableUsers": {
"singular": "Reactivar {n} usuario",
"plural": "Reactivar {n} usuarios"
},
"addUser": {
"singular": "Agregar usuario",
"plural": "Agregar usuarios"
},
"deleteUser": {
"singular": "Borrar usuario",
"plural": "Borrar usuarios"
},
"deletedUser": {
"singular": "{n} usuario eliminado.",
"plural": "{n} usuarios eliminados."
},
"disabledUser": {
"singular": "{n} usuario desactivado.",
"plural": "{n} usuarios desactivados."
},
"enabledUser": {
"singular": "{n} usuario activado.",
"plural": "{n} usuarios activados."
},
"announceTo": {
"singular": "Anunciar a {n} usuario",
"plural": "Anunciar a {n} usuarios"
},
"appliedSettings": {
"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 {n} usuario",
"plural": "Extender la expiración para {n} usuarios"
},
"extendedExpiry": {
"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

@@ -1,31 +1,24 @@
{
"meta": {
"name": "Francais (FR)",
"name": "Français (FR)",
"author": "https://github.com/Killianbe"
},
"strings": {
"invites": "Invite",
"invites": "Invitations",
"accounts": "Comptes",
"settings": "Reglages",
"theme": "Thème",
"settings": "Réglages",
"inviteMonths": "Mois",
"inviteDays": "Jours",
"inviteHours": "Heures",
"inviteMinutes": "Minutes",
"inviteNumberOfUses": "Nombre d'utilisateur",
"inviteNumberOfUses": "Nombre d'utilisateurs",
"warning": "Attention",
"inviteInfiniteUsesWarning": "les invitations infinies peuvent être utilisées abusivement",
"inviteSendToEmail": "Envoyer à",
"login": "S'identifier",
"logout": "Se déconecter",
"create": "Créer",
"apply": "Appliquer",
"delete": "Effacer",
"submit": "Soumettre",
"name": "Nom",
"date": "Date",
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"emailAddress": "Addresse Email",
"lastActiveTime": "Dernière activité",
"from": "De",
"user": "Utilisateur",
@@ -34,43 +27,84 @@
"commitNoun": "Commettre",
"newUser": "Nouvel utilisateur",
"profile": "Profil",
"success": "Succès",
"error": "Erreur",
"unknown": "Inconnu",
"modifySettings": "Modifier les paramètres",
"modifySettingsDescription": "Appliquez les paramètres à partir d'un profil existant ou obtenez-les directement auprès d'un utilisateur.",
"applyHomescreenLayout": "Appliquer la disposition de l'écran d'accueil",
"sendDeleteNotificationEmail": "Envoyer un e-mail de notification",
"sendDeleteNotifiationExample": "Votre compte a été supprimé. ",
"settingsRestartRequired": "Redémarrage nécessaire ",
"settingsRestartRequiredDescription": "Un redémarrage est nécessaire pour appliquer certains paramètres que vous avez modifiés. Redémarrer maintenant ou plus tard?",
"settingsApplyRestartLater": "Appliquer, redémarrer plus tard ",
"settingsApplyRestartNow": "Appliquer et redémarrer ",
"sendDeleteNotificationEmail": "Envoyer un message de notification",
"sendDeleteNotifiationExample": "Votre compte a été supprimé.",
"settingsRestartRequired": "Redémarrage nécessaire",
"settingsRestartRequiredDescription": "Un redémarrage est nécessaire pour appliquer certains paramètres que vous avez modifiés. Redémarrer maintenant ou plus tard ?",
"settingsApplyRestartLater": "Appliquer, redémarrer plus tard",
"settingsApplyRestartNow": "Appliquer et redémarrer",
"settingsApplied": "Paramètres appliqués.",
"settingsRefreshPage": "Actualisez la page dans quelques secondes ",
"settingsRequiredOrRestartMessage": "Remarque: {n} indique un champ obligatoire, {n} indique que les modifications nécessitent un redémarrage. ",
"settingsRefreshPage": "Actualisez la page dans quelques secondes.",
"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. ",
"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",
"userProfilesLibraries": "Bibliothèques",
"addProfile": "Ajouter un profil",
"addProfileDescription": "Créez un utilisateur Jellyfin et configurez-le, puis sélectionnez-le ci-dessous. Lorsque ce profil est appliqué à une invitation, de nouveaux utilisateurs seront créés avec les paramètres. ",
"addProfileDescription": "Créez un utilisateur Jellyfin et configurez-le, puis sélectionnez-le ci-dessous. Lorsque ce profil est appliqué à une invitation, les nouveaux utilisateurs seront créés avec ces paramètres.",
"addProfileNameOf": "Nom de profil",
"addProfileStoreHomescreenLayout": "Enregistrer la disposition de l'écran d'accueil",
"inviteNoUsersCreated": "Aucun pour l'instant!",
"inviteUsersCreated": "Utilisateurs créer",
"inviteNoUsersCreated": "Aucun pour l'instant !",
"inviteUsersCreated": "Utilisateurs créés",
"inviteNoProfile": "Aucun profil",
"copy": "Copier",
"inviteDateCreated": "Créer",
"inviteRemainingUses": "Utilisations restantes",
"inviteNoInvites": "Aucune",
"inviteExpiresInTime": "Expires dans {n}",
"notifyEvent": "Notifier sur:",
"notifyEvent": "Notifier sur :",
"notifyInviteExpiry": "À l'expiration",
"notifyUserCreation": "à la création de l'utilisateur"
"notifyUserCreation": "à la création de l'utilisateur",
"label": "Nom",
"settingsRestarting": "Redémarrage…",
"settingsRestart": "Redémarrer",
"announce": "Annoncer",
"subject": "Sujet",
"message": "Message",
"markdownSupported": "Markdown est pris en charge.",
"customizeMessagesDescription": "Si vous ne souhaitez pas utiliser les modèles d'e-mails de jfa-go, vous pouvez créer les vôtres à l'aide de Markdown.",
"variables": "Variables",
"preview": "Aperçu",
"reset": "Réinitialisation",
"customizeMessages": "Personnaliser les e-mails",
"inviteDuration": "Durée de l'invitation",
"advancedSettings": "Paramètres avancés",
"userExpiry": "Expiration de l'utilisateur",
"updates": "Mises à jour",
"update": "Mise à jour",
"download": "Téléchargement",
"search": "Recherche",
"conditionals": "Conditions",
"userExpiryDescription": "Un laps de temps spécifié après chaque inscription, jfa-go supprimera / désactivera le compte. Vous pouvez modifier ce comportement dans les paramètres.",
"donate": "Faire un don",
"extendExpiry": "Prolonger l'expiration",
"contactThrough": "Contacté par :",
"sendPIN": "Demandez à l'utilisateur d'envoyer le code PIN ci-dessous au bot.",
"select": "Sélectionner",
"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 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}.",
@@ -78,26 +112,37 @@
"createProfile": "Profil créé {n}.",
"saveSettings": "Les paramètres ont été enregistrés",
"setOmbiDefaults": "Valeurs par défaut de Ombi.",
"errorConnection": "Impossible de se connecter à jfa-go.",
"error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.",
"errorSettingsAppliedNoHomescreenLayout": "Les paramètres ont été appliqués, mais l'application de la disposition de l'écran d'accueil a peut-être échoué.",
"errorHomescreenAppliedNoSettings": "La disposition de l'écran d'accueil a été appliquée, mais l'application des paramètres a peut-être échoué.",
"errorSettingsFailed": "L'application a échoué.",
"errorLoginBlank": "Le nom d'utilisateur et / ou le mot de passe sont vides",
"errorUnknown": "Erreur inconnue.",
"errorBlankFields": "Les champs sont vides",
"errorDeleteProfile": "Échec de la suppression du profil {n}",
"errorLoadProfiles": "Échec du chargement des profils.",
"errorCreateProfile": "Échec de la création du profil {n}",
"errorSetDefaultProfile": "Échec de la définition du profil par défaut",
"errorSetDefaultProfile": "Échec de la définition du profil par défaut.",
"errorLoadUsers": "Échec du chargement des utilisateurs.",
"errorSaveSettings": "Impossible d'enregistrer les paramètres.",
"errorLoadSettings": "Échec du chargement des paramètres.",
"errorSetOmbiDefaults": "Impossible de stocker les valeurs par défaut d'Ombi.",
"errorLoadOmbiUsers": "Échec du chargement des utilisateurs Ombi.",
"errorChangedEmailAddress": "Impossible de modifier l'adresse e-mail de {n}.",
"errorFailureCheckLogs": "Échec (vérifier la console / les journaux)",
"errorPartialFailureCheckLogs": "Panne partielle (vérifier la console / les journaux)"
"errorPartialFailureCheckLogs": "Panne partielle (vérifier la console / les journaux)",
"errorUserCreated": "Echec lors de la création de l'utilisateur {n}.",
"errorSendWelcomeEmail": "Echec lors de l'envoi du message de bienvenue (vérifier la console/les journaux)",
"sentAnnouncement": "Annonce envoyée.",
"saveEmail": "Email enregistré.",
"errorSaveEmail": "Échec de l'enregistrement de l'e-mail.",
"updateApplied": "Mise à jour appliquée, veuillez redémarrer.",
"errorApplyUpdate": "Échec de l'application de la mise à jour, essayez manuellement.",
"errorCheckUpdate": "Échec de la vérification de la mise à jour.",
"updateAvailable": "Une nouvelle mise à jour est disponible, vérifiez les paramètres.",
"noUpdatesAvailable": "Aucune nouvelle mise à jour disponible.",
"telegramVerified": "Compte Telegram vérifié.",
"updateAppliedRefresh": "Mise à jour appliquée, veuillez actualiser.",
"accountConnected": "Compte connecté.",
"savedAnnouncement": "Annonce enregistrée.",
"setOmbiProfile": "Profil ombi enregistré.",
"errorSetOmbiProfile": "Echec de la sauvegarde du profil ombi."
},
"quantityStrings": {
"modifySettingsFor": {
@@ -123,6 +168,38 @@
"appliedSettings": {
"singular": "Appliquer le paramètre {n} utilisteur.",
"plural": "Appliquer les paramètres {n} utilisteurs."
},
"announceTo": {
"singular": "Annonce à {n} utilisateur",
"plural": "Annonce à {n} utilisateurs"
},
"enabledUser": {
"plural": "{n} utilisateurs activés.",
"singular": "{n} utilisateur activé."
},
"extendExpiry": {
"singular": "Prolonger l'expiration pour {n} utilisateur",
"plural": "Prolonger l'expiration pour {n} utilisateurs"
},
"extendedExpiry": {
"singular": "Expiration prolongée pour {n} utilisateur.",
"plural": "Expiration prolongée pour {n} utilisateurs."
},
"disableUsers": {
"singular": "Désactiver {n} utilisateur",
"plural": "Désactiver {n} utilisateurs"
},
"reEnableUsers": {
"singular": "Ré-activer {n} utilisateur",
"plural": "Ré-activer {n} utilisateurs"
},
"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"
}
}
}
}

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

@@ -0,0 +1,195 @@
{
"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",
"create": "Létrehozás",
"apply": "Alkalmaz",
"select": "Kiválasztás",
"name": "Név",
"date": "Dátum",
"setExpiry": "Lejárat beállítása",
"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ó",
"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",
"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": "",
"errorSettingsAppliedNoHomescreenLayout": "",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"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": ""
}
}
}

132
lang/admin/id-id.json Normal file
View File

@@ -0,0 +1,132 @@
{
"meta": {
"name": "Bahasa Indonesia (ID)"
},
"strings": {
"invites": "Undangan",
"accounts": "Akun",
"settings": "Pengaturan",
"inviteDays": "Hari",
"inviteHours": "Jam",
"inviteMinutes": "Menit",
"inviteNumberOfUses": "Jumlah penggunaan",
"warning": "Peringatan",
"inviteInfiniteUsesWarning": "Undangan dalam jumlah tak terbatas dapat disalahgunakan",
"inviteSendToEmail": "Dikirim kepada",
"create": "Buat",
"apply": "Terapkan",
"name": "Nama",
"date": "Tanggal",
"lastActiveTime": "Terakhir Aktif",
"from": "Dari",
"user": "Pengguna",
"aboutProgram": "Tentang",
"version": "Versi",
"commitNoun": "Commit",
"newUser": "Pengguna Baru",
"profile": "Profil",
"unknown": "Tidak diketahui",
"label": "Label",
"modifySettings": "Ganti Pengaturan",
"modifySettingsDescription": "Terapkan pengaturan dari profil yang ada, atau dapatkan langsung dari pengguna.",
"applyHomescreenLayout": "Terapkan tata letak layar beranda",
"sendDeleteNotificationEmail": "Kirim email notifikasi",
"sendDeleteNotifiationExample": "Akun anda telah dihapus.",
"settingsRestart": "Mulai ulang",
"settingsRestarting": "Mengulang kembali…",
"settingsRestartRequired": "Mulai ulang diperlukan",
"settingsRestartRequiredDescription": "Mulai ulang diperlukan untuk menerapkan beberapa pengaturan yang Anda ubah. Mulai ulang sekarang atau nanti?",
"settingsApplyRestartLater": "Terapkan, mulai ulang nanti",
"settingsApplyRestartNow": "Terapkan & mulai ulang",
"settingsApplied": "Pengaturan diterapkan.",
"settingsRefreshPage": "Segarkan halaman dalam beberapa detik.",
"settingsRequiredOrRestartMessage": "Catatan: {n} harus diisi, {n} mengindikasikan perubahan memerlukan mulai ulang.",
"settingsSave": "Simpan",
"ombiUserDefaults": "Default pengguna Ombi",
"ombiUserDefaultsDescription": "Buat pengguna Ombi dan konfigurasikan, lalu pilih di bawah. Pengaturan / izinnya akan disimpan dan diterapkan ke pengguna Ombi baru yang dibuat oleh jfa-go",
"userProfiles": "Profil Pengguna",
"userProfilesDescription": "Profil diterapkan ke pengguna saat mereka membuat akun. Profil mencakup hak akses perpustakaan dan tata letak layar utama.",
"userProfilesIsDefault": "Default",
"userProfilesLibraries": "Pustaka",
"addProfile": "Tambahkan Profil",
"addProfileDescription": "Buat pengguna Jellyfin dan konfigurasikan, lalu pilih di bawah. Saat profil ini diterapkan ke undangan, pengguna baru akan dibuat dengan pengaturan seperti ini.",
"addProfileNameOf": "Nama Profil",
"addProfileStoreHomescreenLayout": "Simpan tata letak layar beranda",
"inviteNoUsersCreated": "Belum ada!",
"inviteUsersCreated": "Pengguna yang telah dibuat",
"inviteNoProfile": "Tidak ada profil",
"inviteDateCreated": "Dibuat",
"inviteRemainingUses": "Penggunaan yang tersisa",
"inviteNoInvites": "Tidak ada",
"inviteExpiresInTime": "Kadaluarsa dalam {n}",
"notifyEvent": "Beritahu pada:",
"notifyInviteExpiry": "Saat kadaluarsa",
"notifyUserCreation": "Saat pembuatan pengguna",
"variables": "Variabel",
"preview": "Pratinjau",
"reset": "Setel ulang",
"customizeMessages": "Sesuaikan Email",
"customizeMessagesDescription": "Jika Anda tidak ingin menggunakan templat email jfa-go, Anda dapat membuatnya sendiri menggunakan Markdown.",
"announce": "Mengumumkan",
"subject": "Subjek Email",
"message": "Pesan",
"markdownSupported": "Markdown didukung."
},
"notifications": {
"changedEmailAddress": "Alamat email {n} diubah.",
"userCreated": "Pengguna {n} dibuat.",
"createProfile": "Membuat profil {n}.",
"saveSettings": "Pengaturan telah disimpan",
"setOmbiDefaults": "Default ombi tersimpan.",
"errorSettingsAppliedNoHomescreenLayout": "Pengaturan telah diterapkan, tetapi menerapkan tata letak layar utama mungkin gagal.",
"errorHomescreenAppliedNoSettings": "Tata letak layar beranda diterapkan, tetapi menerapkan pengaturan mungkin gagal.",
"errorSettingsFailed": "Aplikasi gagal.",
"errorBlankFields": "Isian dibiarkan kosong",
"errorDeleteProfile": "Gagal menghapus profil {n}",
"errorLoadProfiles": "Gagal memuat profil.",
"errorCreateProfile": "Gagal membuat profil {n}",
"errorSetDefaultProfile": "Gagal menyetel profil default.",
"errorLoadUsers": "Gagal memuat pengguna.",
"errorLoadSettings": "Gagal memuat pengaturan.",
"errorSetOmbiDefaults": "Gagal menyimpan default ombi.",
"errorLoadOmbiUsers": "Gagal memuat pengguna ombi.",
"errorChangedEmailAddress": "Tidak dapat mengubah alamat email {n}.",
"errorFailureCheckLogs": "Gagal (periksa konsol / log)",
"errorPartialFailureCheckLogs": "Kegagalan sebagian (periksa konsol / log)",
"errorUserCreated": "Gagal membuat pengguna {n}.",
"errorSendWelcomeEmail": "Gagal mengirim email selamat datang (periksa konsol / log)",
"saveEmail": "Email disimpan.",
"sentAnnouncement": "Pengumuman dikirim.",
"errorSaveEmail": "Gagal menyimpan email."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Ubah Setelan untuk {n} pengguna",
"plural": "Ubah Setelan untuk {n} pengguna"
},
"deleteNUsers": {
"singular": "Hapus {n} pengguna",
"plural": "Hapus {n} pengguna"
},
"addUser": {
"singular": "Tambahkan pengguna",
"plural": "Tambahkan pengguna"
},
"deleteUser": {
"singular": "Hapus pengguna",
"plural": "Hapus pengguna"
},
"deletedUser": {
"singular": "Menghapus {n} pengguna.",
"plural": "Menghapus {n} pengguna."
},
"appliedSettings": {
"singular": "Pengaturan diterapkan pada {n} pengguna.",
"plural": "Pengaturan diterapkan pada {n} pengguna."
},
"announceTo": {
"singular": "Umumkan kepada {n} pengguna",
"plural": "Umumkan kepada {n} pengguna"
}
}
}

View File

@@ -6,25 +6,17 @@
"invites": "Uitnodigingen",
"accounts": "Accounts",
"settings": "Instellingen",
"theme": "Thema",
"inviteDays": "Dagen",
"inviteHours": "Uren",
"inviteMinutes": "Minuten",
"inviteNumberOfUses": "Aantal keer gebruikt",
"inviteNumberOfUses": "Aantal keer te gebruiken",
"warning": "Waarschuwing",
"inviteInfiniteUsesWarning": "ongelimiteerde uitnodigingen kunnen misbruikt worden",
"inviteSendToEmail": "Stuur naar",
"login": "Inloggen",
"logout": "Uitloggen",
"create": "Aanmaken",
"apply": "Toepassen",
"delete": "Verwijderen",
"submit": "Verstuur",
"name": "Naam",
"date": "Datum",
"username": "Gebruikersnaam",
"password": "Wachtwoord",
"emailAddress": "Email adres",
"lastActiveTime": "Laatst actief",
"from": "Van",
"user": "Gebruiker",
@@ -33,24 +25,22 @@
"commitNoun": "Commit",
"newUser": "Nieuwe gebruiker",
"profile": "Profiel",
"success": "Success",
"error": "Fout",
"unknown": "Onbekend",
"modifySettings": "Instellingen aanpassen",
"modifySettingsDescription": "Pas instellingen van een bestaand profiel toe, of neem ze direct over van een gebruiker.",
"applyHomescreenLayout": "Sla startpagina indeling op",
"sendDeleteNotificationEmail": "Stuur meldingsemail",
"sendDeleteNotificationEmail": "Stuur melding",
"sendDeleteNotifiationExample": "Je account is verwijderd.",
"settingsRestartRequired": "Herstart nodig",
"settingsRestartRequiredDescription": "Er is een herstart nodig om de wijzigingen door te voeren. Herstart nu of later?",
"settingsApplyRestartLater": "Sla op, herstart later",
"settingsApplyRestartNow": "Sla op & herstart",
"settingsApplied": "Wijzigingen doorgevoerd.",
"settingsRefreshPage": "Ververs de pagina over enkele seconden",
"settingsRefreshPage": "Ververs de pagina over enkele seconden.",
"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",
@@ -59,48 +49,100 @@
"addProfileDescription": "Maak een Jellyfin gebruiker aan met de gewenste instellingen en selecteer deze hieronder. Wanneer dit profiel wordt toegepast op een uitnodiging, worden nieuwe gebruikers aangemaakt met deze instellingen.",
"addProfileNameOf": "Profielnaam",
"addProfileStoreHomescreenLayout": "Sla startpaginaindeling op",
"inviteNoUsersCreated": "Nog geen!",
"inviteUsersCreated": "Aangemaakte gebruikers",
"inviteNoProfile": "Geen profiel",
"copy": "Kopiëer",
"inviteDateCreated": "Aangemaakt",
"inviteRemainingUses": "Resterend aantal gebruiken",
"inviteRemainingUses": "Resterend aantal keer te gebruiken",
"inviteNoInvites": "Geen",
"inviteExpiresInTime": "Verloopt over {n}",
"notifyEvent": "Meldingen:",
"notifyInviteExpiry": "Bij verloop",
"notifyUserCreation": "Bij aanmaken gebruiker"
"notifyUserCreation": "Bij aanmaken gebruiker",
"label": "Label",
"settingsRestart": "Herstart",
"settingsRestarting": "Aan het herstarten…",
"announce": "Aankondiging",
"markdownSupported": "Markdown wordt ondersteund.",
"subject": "Onderwerp",
"message": "Bericht",
"variables": "Variabelen",
"customizeMessagesDescription": "Als je de e-mailsjablonen van jfa-go niet wilt gebruiken, kun je met gebruik van Markdown je eigen aanmaken.",
"preview": "Voorbeeld",
"reset": "Reset",
"customizeMessages": "E-mails aanpassen",
"inviteDuration": "Geldigheidsduur uitnodiging",
"userExpiryDescription": "Een bepaalde tijd na elke aanmelding, wordt de account verwijderd/uitgeschakeld door jfa-go. Dit kan aangepast worden in de instellingen.",
"userExpiry": "Gebruikersverloop",
"extendExpiry": "Verleng verloop",
"updates": "Updates",
"update": "Bijwerken",
"download": "Download",
"search": "Zoeken",
"advancedSettings": "Geavanceerde instellingen",
"inviteMonths": "Maanden",
"conditionals": "Voorwaarden",
"donate": "Doneer",
"contactThrough": "Stuur bericht via:",
"sendPIN": "Vraag de gebruiker om onderstaande pincode naar de bot te sturen.",
"searchDiscordUser": "Begin de Discord gebruikersnaam te typen om de gebruiker te vinden.",
"linkMatrixDescription": "Vul de gebruikersnaam en wachtwoord in van de gebruiker om als bot te gebruiken. De app start zodra ze zijn verstuurd.",
"select": "Selecteer",
"findDiscordUser": "Zoek Discord gebruiker",
"matrixHomeServer": "Adres home server",
"templates": "Sjablonen",
"templateEnterName": "Voer een naam in om dit sjabloon op te slaan.",
"saveAsTemplate": "Sla op als 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": "Email adres van {n} gewijzigd.",
"changedEmailAddress": "E-mailadres van {n} gewijzigd.",
"userCreated": "Gebruiker {n} aangemaakt.",
"createProfile": "Profiel {n} aangemaakt.",
"saveSettings": "De instellingen zijn opgeslagen",
"setOmbiDefaults": "De ombi standaardinstellingen zijn opgeslagen.",
"errorConnection": "Kon geen verbinding maken met jfa-go.",
"error401Unauthorized": "Geen toegang. Probeer de pagina te vernieuwen.",
"errorSettingsAppliedNoHomescreenLayout": "De instellingen zijn toegepast, maar wijzigen van de startpaginaindeling is misschien mislukt.",
"errorHomescreenAppliedNoSettings": "Startpaginaindeling toegepast, maar opslaan van instellingen is misschien mislukt.",
"errorSettingsFailed": "Opslaan mislukt.",
"errorLoginBlank": "De gebruikersnaam en/of wachtwoord is leeg.",
"errorUnknown": "Onbekende fout.",
"errorBlankFields": "Velden leeggelaten",
"errorDeleteProfile": "Verwijderen van profiel {n} mislukt",
"errorLoadProfiles": "Fout bij het laden van profielen.",
"errorCreateProfile": "Aanmaken van profile {n} mislukt",
"errorSetDefaultProfile": "Fout bij instellen van standaardprofiel.",
"errorLoadUsers": "Laden van gebruikers mislukt.",
"errorSaveSettings": "Opslaan van instellingen mislukt.",
"errorLoadSettings": "Laden van instellingen mislukt.",
"errorSetOmbiDefaults": "Opslaan van ombi standaardinstellingen mislukt.",
"errorLoadOmbiUsers": "Laden van ombi gebruikers mislukt.",
"errorChangedEmailAddress": "Wijzigen van emailadres van {n} mislukt.",
"errorChangedEmailAddress": "Wijzigen van e-mailadres van {n} mislukt.",
"errorFailureCheckLogs": "Mislukt (controleer console/logbestanden)",
"errorPartialFailureCheckLogs": "Gedeeltelijke fout (controleer console/logbestanden)"
"errorPartialFailureCheckLogs": "Gedeeltelijke fout (controleer console/logbestanden)",
"errorSendWelcomeEmail": "Versturen van welkomstbericht is mislukt (zie console/logs)",
"errorUserCreated": "Aanmaken van gebruiker {n} is mislukt.",
"sentAnnouncement": "Aankondiging verzonden.",
"saveEmail": "E-mail opgeslagen.",
"errorSaveEmail": "Opslaan van e-mail mislukt.",
"updateApplied": "De update is geïnstalleerd, doe alsjeblieft een herstart.",
"errorApplyUpdate": "Installatie van update mislukt, probeer handmatig.",
"errorCheckUpdate": "Controleren op update mislukt.",
"updateAvailable": "Er is een nieuwe update beschikbaar, kijk bij instellingen.",
"noUpdatesAvailable": "Geen nieuwe updates beschikbaar.",
"telegramVerified": "Telegram-account goedgekeurd.",
"updateAppliedRefresh": "Update toegepast, ververs alsjeblieft.",
"accountConnected": "Account gekoppeld.",
"savedAnnouncement": "Aankondiging opgeslagen.",
"setOmbiProfile": "Opgeslagen ombi-profiel.",
"errorSetOmbiProfile": "Opslaan van ombi-profiel mislukt."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Wijzig instellingen voor {n} gebruiker",
@@ -125,6 +167,38 @@
"appliedSettings": {
"singular": "Instellingen toegepast op {n} gebruiker.",
"plural": "Instellingen toegepast op {n} gebruikers."
},
"announceTo": {
"singular": "Aankondigen aan {n} gebruikers",
"plural": "aankondigen aan {n} gebruikerss"
},
"extendExpiry": {
"singular": "Verleng verloop voor {n} gebruiker",
"plural": "Verleng verloop voor {n} gebruikers"
},
"extendedExpiry": {
"singular": "Verloop uitgesteld voor {n} gebruiker.",
"plural": "Verloop uitgesteld voor {n} gebruikers."
},
"disableUsers": {
"singular": "Schakel {n} gebruiker uit",
"plural": "Schakel {n} gebruikers uit"
},
"reEnableUsers": {
"singular": "Schakel {n} gebruiker opnieuw in",
"plural": "Schakel {n} gebruikers opnieuw in"
},
"disabledUser": {
"singular": "{n} gebruiker uitgeschakeld.",
"plural": "{n} gebruikers uitgeschakeld."
},
"enabledUser": {
"singular": "{n} gebruiker ingeschakeld.",
"plural": "{n} gebruikers ingeschakeld."
},
"setExpiry": {
"singular": "Stel verloop in voor {n} gebruiker",
"plural": "Stel verloop in voor {n} gebruikers"
}
}
}
}

201
lang/admin/pl-pl.json Normal file
View File

@@ -0,0 +1,201 @@
{
"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": "",
"create": "",
"apply": "",
"select": "",
"name": "Imię",
"date": "Data",
"setExpiry": "",
"updates": "Aktualizacje",
"update": "Aktualizacja",
"download": "Pobierz",
"search": "Szukaj",
"advancedSettings": "Zaawansowane",
"lastActiveTime": "Ostatnia aktywność",
"from": "Od",
"user": "Użytkownik",
"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",
"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.",
"errorSettingsAppliedNoHomescreenLayout": "Zastosowano ustawienia, ale zastosowanie układu ekranu głównego mogło się nie powieść.",
"errorHomescreenAppliedNoSettings": "",
"errorSettingsFailed": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"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": ""
}
}
}

204
lang/admin/pt-br.json Normal file
View File

@@ -0,0 +1,204 @@
{
"meta": {
"name": "Português (BR)"
},
"strings": {
"invites": "Convites",
"accounts": "Contas",
"settings": "Configurações",
"inviteDays": "Dias",
"inviteHours": "Horas",
"inviteMinutes": "Minutos",
"inviteNumberOfUses": "Números de usuários",
"warning": "Aviso",
"inviteInfiniteUsesWarning": "convites infinitos podem ser usados de forma abusiva",
"inviteSendToEmail": "Enviar para",
"create": "Criar",
"apply": "Aplicar",
"name": "Nome",
"date": "Data",
"lastActiveTime": "Ativo pela última vez",
"from": "De",
"user": "Usuário",
"aboutProgram": "Sobre",
"version": "Versão",
"commitNoun": "Commit",
"newUser": "Novo Usuário",
"profile": "Perfil",
"unknown": "Desconhecido",
"label": "Rótulo",
"modifySettings": "Modificar configurações",
"modifySettingsDescription": "Aplique as configurações de um perfil existente ou obtenha-as diretamente de um usuário.",
"applyHomescreenLayout": "Aplicar layout na tela inicial",
"sendDeleteNotificationEmail": "Enviar mensagem de notificação",
"sendDeleteNotifiationExample": "Sua conta foi deletada.",
"settingsRestartRequired": "Necessário reiniciar",
"settingsRestartRequiredDescription": "É necessário reiniciar para aplicar algumas configurações alteradas. Deseja reiniciar agora ou mais tarde?",
"settingsApplyRestartLater": "Aplicar, reiniciar mais tarde",
"settingsApplyRestartNow": "Aplicar e reiniciar",
"settingsApplied": "Configurações aplicada.",
"settingsRefreshPage": "Atualize a página em alguns segundos.",
"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 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",
"userProfilesLibraries": "Bibliotecas",
"addProfile": "Adicionar Perfil",
"addProfileDescription": "Crie um usuário no Jellyfin, configure e selecione abaixo. Quando este perfil é aplicado a um convite, novos usuários serão criados com as mesma configurações.",
"addProfileNameOf": "Nome do perfil",
"addProfileStoreHomescreenLayout": "Layout da tela inicial da loja",
"inviteNoUsersCreated": "Nenhum ainda!",
"inviteUsersCreated": "Usuários criado",
"inviteNoProfile": "Sem Perfil",
"inviteDateCreated": "Criado",
"inviteRemainingUses": "Uso restantes",
"inviteNoInvites": "Nenhum",
"inviteExpiresInTime": "Expira em {n}",
"notifyEvent": "Notificar em:",
"notifyInviteExpiry": "No vencimento",
"notifyUserCreation": "Na criação do usuário",
"settingsRestart": "Reiniciar",
"settingsRestarting": "Reiniciando…",
"announce": "Anunciar",
"subject": "Assunto",
"message": "Mensagem",
"markdownSupported": "Suporte a Markdown.",
"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": "Redefinir",
"customizeMessages": "Customizar Emails",
"userExpiryDescription": "Após um determinado período de tempo de cada inscrição, o jfa-go apagará/desabilitará a conta. Você pode alterar essa opção nas configurações.",
"inviteDuration": "Duração do Convite",
"userExpiry": "Vencimento do Usuário",
"extendExpiry": "Extender o vencimento",
"updates": "Atualizações",
"update": "Atualizar",
"download": "Download",
"search": "Procurar",
"advancedSettings": "Configurações Avançada",
"inviteMonths": "Meses",
"conditionals": "Condicionais",
"donate": "Doar",
"contactThrough": "Contato através:",
"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",
"linkMatrixDescription": "Digite o nome de usuário e a senha para usar como bot. Depois de enviado, o aplicativo será reiniciado.",
"select": "Selecionar",
"templates": "Modelos",
"matrixHomeServer": "Endereço do servidor local",
"saveAsTemplate": "Salvar o modelo",
"deleteTemplate": "Deletar 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}.",
"userCreated": "Usuário {n} criado.",
"createProfile": "Perfil {n} criado.",
"saveSettings": "As configurações foram salvas",
"setOmbiDefaults": "Padrões do ombi armazenados.",
"errorSettingsAppliedNoHomescreenLayout": "As configurações foram aplicadas, mas a aplicação do layout da tela inicial pode ter falhado.",
"errorHomescreenAppliedNoSettings": "O layout da tela inicial foi aplicado, mas a aplicação das configurações pode ter falhado.",
"errorSettingsFailed": "Falha na aplicação.",
"errorBlankFields": "Os campos foram deixados em branco",
"errorDeleteProfile": "Falha ao excluir perfil {n}",
"errorLoadProfiles": "Falha ao carregar perfis.",
"errorCreateProfile": "Falha ao criar perfil {n}",
"errorSetDefaultProfile": "Falha ao definir o perfil padrão.",
"errorLoadUsers": "Falha ao carregar usuários.",
"errorLoadSettings": "Falha ao carregar as configurações.",
"errorSetOmbiDefaults": "Falha em armazenar os padrões ombi.",
"errorLoadOmbiUsers": "Falha ao carregar usuários ombi.",
"errorChangedEmailAddress": "Não foi possível alterar o endereço de e-mail de {n}.",
"errorFailureCheckLogs": "Falha (verificar console/logs)",
"errorPartialFailureCheckLogs": "Falha parcial (verificar console/logs)",
"errorUserCreated": "Falha ao criar o usuário {n}.",
"errorSendWelcomeEmail": "Falha ao enviar mensagem de boas-vindas (verifique console/logs)",
"sentAnnouncement": "Comunicado enviado.",
"saveEmail": "Email salvo.",
"errorSaveEmail": "Falha ao salvar o email.",
"updateApplied": "Atualização aplicada, reinicie.",
"errorApplyUpdate": "Falha ao aplicar a atualização, tente manualmente.",
"updateAvailable": "Uma nova atualização está disponível, verifique as configurações.",
"errorCheckUpdate": "Falha ao verificar atualizações.",
"noUpdatesAvailable": "Nenhuma atualização disponível.",
"telegramVerified": "Conta do Telegram verificada.",
"updateAppliedRefresh": "Atualização instalada, atualize.",
"accountConnected": "Conta conectada.",
"savedAnnouncement": "Anúncio salvo.",
"setOmbiProfile": "Perfil ombi armazenado.",
"errorSetOmbiProfile": "Falha ao armazenar o perfil ombi."
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Modificar configurações para usuário {n}",
"plural": "Modificar configurações para usuários {n}"
},
"deleteNUsers": {
"singular": "Excluir usuário {n}",
"plural": "Excluir usuários {n}"
},
"addUser": {
"singular": "Adicionar usuário",
"plural": "Adicionar usuários"
},
"deleteUser": {
"singular": "Deletar Usuário",
"plural": "Deletar Usuários"
},
"deletedUser": {
"singular": "Excluiu usuário {n}.",
"plural": "Excluiu usuários {n}."
},
"appliedSettings": {
"singular": "Configurações aplicada ao usuário {n}.",
"plural": "Configurações aplicada ao usuários {n}."
},
"announceTo": {
"singular": "Comunicar o usuário {n}",
"plural": "Comunicar os usuários {n}"
},
"extendExpiry": {
"singular": "Extender o vencimento para {n}",
"plural": "Extender o vencimento para {n} usuários"
},
"extendedExpiry": {
"plural": "Extender o vencimento para {n} usuários.",
"singular": "Extender vencimento para {n}."
},
"disableUsers": {
"singular": "Desativar {n} usuário",
"plural": "Desativar {n} usuários"
},
"reEnableUsers": {
"singular": "Reativar {n} usuário",
"plural": "Reativar {n} usuários"
},
"disabledUser": {
"singular": "{n} Usuário desativado.",
"plural": "{n} usuários desativado."
},
"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"
}
}
}

144
lang/admin/sv-se.json Normal file
View File

@@ -0,0 +1,144 @@
{
"meta": {
"name": "Svenska (SV)"
},
"strings": {
"invites": "Inbjudningar",
"accounts": "Konton",
"settings": "Inställningar",
"inviteDays": "Dagar",
"inviteHours": "Timmar",
"inviteMinutes": "Minuter",
"inviteNumberOfUses": "Antal användningar",
"warning": "Varning",
"inviteInfiniteUsesWarning": "inbjudningar med oändligt antal användningar kan missbrukas",
"inviteSendToEmail": "Skicka till",
"create": "Skapa",
"apply": "Tillämpa",
"name": "Namn",
"date": "Datum",
"lastActiveTime": "Senast aktiv",
"from": "Från",
"user": "Användare",
"aboutProgram": "Om",
"version": "Version",
"commitNoun": "Skicka",
"newUser": "Ny användare",
"profile": "Profil",
"unknown": "Okänd",
"label": "Etikett",
"announce": "Meddela",
"subject": "E-postämne",
"message": "Meddelande",
"variables": "Variabler",
"preview": "Förhandsvisning",
"reset": "Återställ",
"customizeMessages": "Anpassa e-post",
"customizeMessagesDescription": "Om du inte vill använda jfa-go's e-postmallar, så kan du skapa dina egna med Markdown.",
"markdownSupported": "Markdown stöds.",
"modifySettings": "Ändra inställningar",
"modifySettingsDescription": "Tillämpa inställningar från en befintlig profil eller kopiera dem direkt från en användare.",
"applyHomescreenLayout": "Tillämpa hemskärmslayout",
"sendDeleteNotificationEmail": "Skicka notifikations e-postmeddelande",
"sendDeleteNotifiationExample": "Ditt konto har raderats.",
"settingsRestart": "Omstart",
"settingsRestarting": "Startar om…",
"settingsRestartRequired": "Omstart krävs",
"settingsRestartRequiredDescription": "En omstart är nödvändig för att tillämpa vissa inställningar du ändrat. Starta om nu eller senare?",
"settingsApplyRestartLater": "Tillämpa, men starta om senare",
"settingsApplyRestartNow": "Tillämpa och starta om",
"settingsApplied": "Inställningarna tillämpade.",
"settingsRefreshPage": "Uppdatera sidan om några sekunder.",
"settingsRequiredOrRestartMessage": "Notera: {n} anger ett obligatoriskt fält, {n} anger att ändringar kräver omstart.",
"settingsSave": "Spara",
"ombiUserDefaults": "Ombi standardanvändare",
"ombiUserDefaultsDescription": "Skapa en Ombi-användare och konfigurera denna, välj sedan denna här nedan. Inställningar/behörigheter lagras och tillämpas på nya Ombi-användare som skapats av jfa-go",
"userProfiles": "Användarprofiler",
"userProfilesDescription": "Profiler tillämpas på användare när de skapar ett konto. En profil inkluderar biblioteksåtkomsträttigheter och layout på hemskärmen.",
"userProfilesIsDefault": "Standard",
"userProfilesLibraries": "Bibliotek",
"addProfile": "Lägg till profil",
"addProfileDescription": "Skapa en Jellyfin-användare och konfigurera samt välj denna här nedan. När den här profilen tillämpas på en inbjudan skapas nya användare med inställningarna.",
"addProfileNameOf": "Profilnamn",
"addProfileStoreHomescreenLayout": "Lagra hemskärmslayout",
"inviteNoUsersCreated": "Ingen än!",
"inviteUsersCreated": "Skapade användare",
"inviteNoProfile": "Ingen profil",
"inviteDateCreated": "Skapad",
"inviteRemainingUses": "Återstående användningar",
"inviteNoInvites": "Ingen",
"inviteExpiresInTime": "Går ut om {n}",
"notifyEvent": "Meddela den:",
"notifyInviteExpiry": "Vid utgång",
"notifyUserCreation": "Vid användarskapande",
"inviteDuration": "Varaktighet för inbjudan",
"userExpiry": "Användarutgång",
"userExpiryDescription": "Efter en angiven tid efter varje registrering så tar jfa-go bort/inaktiverar kontot. Du kan ändra detta beteende i inställningarna.",
"extendExpiry": "Förläng utgång"
},
"notifications": {
"changedEmailAddress": "Ändrad e-postadress för {n}.",
"userCreated": "Användaren {n} har skapats.",
"createProfile": "Skapad profil {n}.",
"saveSettings": "Inställningar sparades",
"saveEmail": "E-post sparad.",
"sentAnnouncement": "Meddelande skickat.",
"setOmbiDefaults": "Lagrade ombi-standardvärden.",
"errorSettingsAppliedNoHomescreenLayout": "Inställningarna tillämpades, men tillämpningen av hemskärmslayout kan ha misslyckats.",
"errorHomescreenAppliedNoSettings": "Hemskärmslayout tillämpades, men tillämpningen av inställningar kan ha misslyckats.",
"errorSettingsFailed": "Tillämpning misslyckades.",
"errorSaveEmail": "Det gick inte att spara e-postmeddelandet.",
"errorBlankFields": "Fält lämnades tomma",
"errorDeleteProfile": "Det gick inte att ta bort profilen {n}",
"errorLoadProfiles": "Det gick inte att läsa in profiler.",
"errorCreateProfile": "Det gick inte att skapa profilen {n}",
"errorSetDefaultProfile": "Det gick inte att ange standardprofil.",
"errorLoadUsers": "Det gick inte att läsa in användare.",
"errorLoadSettings": "Det gick inte att läsa in inställningarna.",
"errorSetOmbiDefaults": "Det gick inte att lagra ombi-standardvärden.",
"errorLoadOmbiUsers": "Det gick inte att ladda ombi-användare.",
"errorChangedEmailAddress": "Det gick inte att ändra e-postadressen för {n}.",
"errorFailureCheckLogs": "Misslyckades (kontrollera konsol/loggar)",
"errorPartialFailureCheckLogs": "Partiellt fel (kontrollera konsol/loggarna)",
"errorUserCreated": "Det gick inte att skapa användaren {n}.",
"errorSendWelcomeEmail": "Det gick inte att skicka välkomstmail (kontrollera konsol/loggar)"
},
"quantityStrings": {
"modifySettingsFor": {
"singular": "Ändra inställningar för {n} användare",
"plural": "Ändra inställningar för {n} användare"
},
"deleteNUsers": {
"singular": "Ta bort {n} användare",
"plural": "Ta bort {n} användare"
},
"addUser": {
"singular": "Lägg till användare",
"plural": "Lägg till användare"
},
"deleteUser": {
"singular": "Radera användare",
"plural": "Radera användare"
},
"deletedUser": {
"singular": "{N} användare borttagen.",
"plural": "{N} användare har tagits bort."
},
"announceTo": {
"singular": "Meddela till {n} användare",
"plural": "Meddela till {n} användare"
},
"appliedSettings": {
"singular": "Tillämpade inställningar för {n} användare.",
"plural": "Tillämpade inställningar för {n} användare."
},
"extendExpiry": {
"plural": "Förläng utgången för {n} användare",
"singular": "Förläng utgången för {n} användare"
},
"extendedExpiry": {
"singular": "Utökad giltighetstid för {n} användare.",
"plural": "Utökad giltighetstid för {n} användare."
}
}
}

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

@@ -0,0 +1,196 @@
{
"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",
"create": "Tạo mới",
"apply": "Áp dụng",
"select": "Chọn",
"name": "Tên",
"date": "Ngày",
"setExpiry": "Đặt hết hạn",
"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",
"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",
"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.",
"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": "",
"errorSaveEmail": "",
"errorBlankFields": "",
"errorDeleteProfile": "",
"errorLoadProfiles": "",
"errorCreateProfile": "",
"errorSetDefaultProfile": "",
"errorLoadUsers": "",
"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": ""
}
}
}

204
lang/admin/zh-hans.json Normal file
View File

@@ -0,0 +1,204 @@
{
"meta": {
"name": "简体中文(CN)"
},
"strings": {
"invites": "邀请",
"accounts": "用户",
"settings": "设置",
"inviteMonths": "月",
"inviteDays": "日",
"inviteHours": "小时",
"inviteMinutes": "分钟",
"inviteNumberOfUses": "使用次数",
"inviteDuration": "邀请时长",
"warning": "警告",
"inviteInfiniteUsesWarning": "无限使用次数的邀请码可能被滥用",
"inviteSendToEmail": "发送到",
"create": "创建",
"apply": "申请",
"select": "选择",
"name": "名称",
"date": "日期",
"updates": "更新",
"update": "更新",
"download": "下载",
"search": "搜索",
"advancedSettings": "高级设置",
"lastActiveTime": "上次活动",
"from": "从",
"user": "用户",
"userExpiry": "用户到期",
"userExpiryDescription": "每次注册后的指定时间jfa-go 将删除/禁用该帐户。您可以在设置中更改此行为。",
"aboutProgram": "关于",
"version": "版本",
"commitNoun": "提交",
"newUser": "新用户",
"profile": "个人资料",
"unknown": "未知",
"label": "标签",
"announce": "宣布",
"templates": "模板",
"subject": "主题",
"message": "信息",
"variables": "变量",
"conditionals": "条件性条款",
"preview": "预览",
"reset": "重设",
"donate": "捐助",
"contactThrough": "联系方式:",
"extendExpiry": "延长有效期",
"customizeMessages": "自定义消息",
"customizeMessagesDescription": "如果不想使用 jfa-go 的消息模板,可以使用 Markdown 创建自己的消息模板。",
"markdownSupported": "已支持Markdown。",
"modifySettings": "修改设置",
"modifySettingsDescription": "应用现有配置文件中的设置,或直接从用户处获取设置。",
"applyHomescreenLayout": "应用主屏幕布局",
"sendDeleteNotificationEmail": "发送通知消息",
"sendDeleteNotifiationExample": "您的帐户已被删除。",
"settingsRestart": "重启",
"settingsRestarting": "正在重启……",
"settingsRestartRequired": "需要重启",
"settingsRestartRequiredDescription": "需要重新启动才能应用您更改的某些设置。现在重启还是稍后重启?",
"settingsApplyRestartLater": "应用,稍后重启",
"settingsApplyRestartNow": "应用并重启",
"settingsApplied": "已应用设置。",
"settingsRefreshPage": "几秒钟后刷新页面。",
"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": "更改了 {n} 的电子邮件地址。",
"userCreated": "用户 {n} 已创建。",
"createProfile": "创建了配置文件{n}。",
"saveSettings": "设置已保存",
"saveEmail": "电子邮件已保存。",
"sentAnnouncement": "公告已发出。",
"savedAnnouncement": "公告已保存。",
"setOmbiDefaults": "存储的ombi默认值。",
"updateApplied": "已应用更新,请重新启动。",
"updateAppliedRefresh": "已应用更新,请刷新。",
"telegramVerified": "Telegram账户已验证。",
"accountConnected": "帐户已连接。",
"errorSettingsAppliedNoHomescreenLayout": "已应用设置,但应用主屏幕布局可能失败。",
"errorHomescreenAppliedNoSettings": "已应用主屏幕布局,但应用设置可能失败。",
"errorSettingsFailed": "应用失败。",
"errorSaveEmail": "电子邮箱保存失败。",
"errorBlankFields": "字段留空",
"errorDeleteProfile": "删除配置文件{n}失败",
"errorLoadProfiles": "加载配置文件失败。",
"errorCreateProfile": "创建配置文件{n}失败",
"errorSetDefaultProfile": "设置默认配置文件失败。",
"errorLoadUsers": "加载用户列表失败。",
"errorLoadSettings": "加载配置列表失败。",
"errorSetOmbiDefaults": "存储Ombi默认值失败。",
"errorLoadOmbiUsers": "加载ombi用户列表失败。",
"errorChangedEmailAddress": "无法更改 {n} 的电子邮件地址。",
"errorFailureCheckLogs": "失败(检查控制台/日志)",
"errorPartialFailureCheckLogs": "部分失败(检查控制台/日志)",
"errorUserCreated": "创建用户{n}失败。",
"errorSendWelcomeEmail": "发送欢迎消息失败(检查控制台/日志)",
"errorApplyUpdate": "无法应用更新,请手动尝试。",
"errorCheckUpdate": "检查更新失败。",
"updateAvailable": "有新更新可用,请检查设置。",
"noUpdatesAvailable": "没有可用的更新。",
"setOmbiProfile": "保存ombi配置文件。",
"errorSetOmbiProfile": "无法保存ombi配置文件。"
},
"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} 个用户的有效期"
},
"extendedExpiry": {
"singular": "延长了 {n} 个用户的有效期。",
"plural": "延长了 {n} 个用户的有效期。"
},
"setExpiry": {
"plural": "为{n}用户设置到期时间",
"singular": "为{n}用户设置到期时间"
}
}
}

201
lang/admin/zh-hant.json Normal file
View File

@@ -0,0 +1,201 @@
{
"meta": {
"name": "繁體中文 (TW)"
},
"strings": {
"invites": "邀請",
"accounts": "帳戶",
"settings": "設置",
"inviteMonths": "月",
"inviteDays": "日",
"inviteHours": "小時",
"inviteMinutes": "分鐘",
"inviteNumberOfUses": "使用次數",
"inviteDuration": "邀請時長",
"warning": "警告",
"inviteInfiniteUsesWarning": "無限使用次數的邀請碼可能被濫用",
"inviteSendToEmail": "發送到",
"create": "創建",
"apply": "應用",
"select": "選擇",
"name": "帳戶名稱",
"date": "日期",
"setExpiry": "設置到期時間",
"updates": "更新",
"update": "更新",
"download": "下載",
"search": "搜尋",
"advancedSettings": "高級設置",
"lastActiveTime": "上次啟用時間",
"from": "從",
"user": "帳戶",
"userExpiry": "帳戶到期",
"userExpiryDescription": "每次註冊后指定的時間jfa-go 將刪除/禁用該帳戶。您可以在設定中更改此行為。",
"aboutProgram": "關於",
"version": "版本",
"commitNoun": "提交",
"newUser": "新帳戶",
"profile": "帳戶資料",
"unknown": "未知",
"label": "標籤",
"logs": "日誌",
"announce": "公告",
"templates": "範本",
"subject": "主題",
"message": "訊息",
"variables": "變數",
"conditionals": "條件",
"preview": "預覽",
"reset": "重設",
"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": "帳戶已連接。",
"errorSettingsAppliedNoHomescreenLayout": "已應用設置,但應用主螢幕佈局可能失敗。",
"errorHomescreenAppliedNoSettings": "已應用主螢幕佈局,但應用設置可能失敗。",
"errorSettingsFailed": "應用失敗。",
"errorSaveEmail": "無法儲存電子郵件。",
"errorBlankFields": "欄位留空",
"errorDeleteProfile": "無法刪除設置文件 {n}",
"errorLoadProfiles": "無法讀取設置文件。",
"errorCreateProfile": "無法創建設置文件 {n}",
"errorSetDefaultProfile": "無法設置預設設置文件。",
"errorLoadUsers": "無法讀取帳戶。",
"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} 個帳戶的到期時間。"
}
}
}

8
lang/common/ar-aa.json Normal file
View File

@@ -0,0 +1,8 @@
{
"meta": {
"name": "العربية (AR)"
},
"strings": {},
"notifications": {},
"quantityStrings": {}
}

48
lang/common/da-dk.json Normal file
View File

@@ -0,0 +1,48 @@
{
"meta": {
"name": "Dansk (DK)"
},
"strings": {
"username": "Brugernavn",
"password": "Adgangskode",
"emailAddress": "E-mail Adresse",
"name": "Navn",
"submit": "Indsend",
"send": "Send",
"success": "Succes",
"continue": "Fortsæt",
"error": "Fejl",
"copy": "Kopiér",
"copied": "Kopiret",
"time24h": "24 timers tid",
"time12h": "12 timers tid",
"linkTelegram": "Link Telegram",
"contactEmail": "Kontakt gennem E-mail",
"contactTelegram": "Kontakt gennem Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Kontakt gennem Discord",
"theme": "Tema",
"refresh": "Opdater",
"required": "Påkrævet",
"login": "Log på",
"logout": "Log ud",
"admin": "Administrator",
"enabled": "Aktiveret",
"disabled": "Deaktiveret",
"reEnable": "Genaktiver",
"disable": "Deaktiver",
"expiry": "Udløb",
"add": "Tilføj",
"edit": "Rediger",
"delete": "Slet"
},
"notifications": {
"errorLoginBlank": "Brugernavnet og/eller adgangskoden blev efterladt tomme.",
"errorConnection": "Kunne ikke oprette forbindelse til jfa-go.",
"errorUnknown": "Ukendt fejl.",
"error401Unauthorized": "Adgang nægtet. Prøv at genindlæse siden.",
"errorSaveSettings": "Kunne ikke gemme indstillingerne."
},
"quantityStrings": {}
}

48
lang/common/de-de.json Normal file
View File

@@ -0,0 +1,48 @@
{
"meta": {
"name": "Deutsch (DE)"
},
"strings": {
"username": "Benutzername",
"password": "Passwort",
"emailAddress": "E-Mail Adresse",
"name": "Name",
"submit": "Absenden",
"send": "Senden",
"success": "Erfolgreich",
"continue": "Weiter",
"error": "Fehler",
"copy": "Kopieren",
"copied": "Kopiert",
"time24h": "24h-Format",
"time12h": "12h-Format",
"linkTelegram": "Link Telegram",
"contactEmail": "Kontakt über E-Mail",
"contactTelegram": "Kontakt über Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Kontakt über Discord",
"theme": "Thema",
"refresh": "Aktualisieren",
"required": "Erforderlich",
"login": "Anmelden",
"logout": "Abmelden",
"admin": "Admin",
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"reEnable": "Wieder aktivieren",
"disable": "Deaktivieren",
"expiry": "Ablaufdatum",
"add": "Hinzufügen",
"edit": "Bearbeiten",
"delete": "Löschen"
},
"notifications": {
"errorLoginBlank": "Der Benutzername und/oder das Passwort wurden nicht ausgefüllt.",
"errorConnection": "Konnte keine Verbindung zu jfa-go herstellen.",
"errorUnknown": "Unbekannter Fehler.",
"error401Unauthorized": "Unberechtigt. Versuch, die Seite zu aktualisieren.",
"errorSaveSettings": "Einstellungen konnten nicht gespeichert werden."
},
"quantityStrings": {}
}

38
lang/common/el-gr.json Normal file
View File

@@ -0,0 +1,38 @@
{
"meta": {
"name": "Ελληνικά (GR)"
},
"strings": {
"username": "Όνομα Χρήστη",
"password": "Κωδικός",
"emailAddress": "Διεύθυνση Email",
"name": "Όνομα",
"submit": "Καταχώρηση",
"success": "Επιτυχία",
"continue": "Συνέχεια",
"error": "Σφάλμα",
"copy": "Αντιγραφή",
"copied": "Αντιγράφηκε",
"time24h": "24 Ώρες",
"time12h": "12 Ώρες",
"theme": "Θέμα",
"login": "Σύνδεση",
"logout": "Αποσύνδεση",
"admin": "Διαχειριστής",
"enabled": "Ενεργοποιημένο",
"disabled": "Απενεργοποιημένο",
"reEnable": "Επανα-ενεργοποίηση",
"disable": "Απενεργοποίηση",
"expiry": "Λήξη",
"edit": "Επεξεργασία",
"delete": "Διαγραφή"
},
"notifications": {
"errorLoginBlank": "Το όνομα χρήστη και/ή ο κωδικός ήταν κενά.",
"errorConnection": "Δεν μπόρεσε να συνδεθεί με το jfa-go.",
"errorUnknown": "Άγνωστο σφάλμα.",
"error401Unauthorized": "Ανεξουσιοδότητος. Προσπαθήστε να κάνετε επαναφόρτωση την σελίδα.",
"errorSaveSettings": "Αποτυχία αποθήκευσης ρυθμίσεων."
},
"quantityStrings": {}
}

48
lang/common/en-gb.json Normal file
View File

@@ -0,0 +1,48 @@
{
"meta": {
"name": "English (GB)"
},
"strings": {
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"name": "Name",
"submit": "Submit",
"send": "Send",
"success": "Success",
"continue": "Continue",
"error": "Error",
"copy": "Copy",
"copied": "Copied",
"time24h": "24h Time",
"time12h": "12h Time",
"linkTelegram": "Link Telegram",
"contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord",
"theme": "Theme",
"refresh": "Refresh",
"required": "Required",
"login": "Login",
"logout": "Logout",
"admin": "Admin",
"enabled": "Enabled",
"disabled": "Disabled",
"reEnable": "Re-enable",
"disable": "Disable",
"expiry": "Expiry",
"add": "Add",
"edit": "Edit",
"delete": "Delete"
},
"notifications": {
"errorLoginBlank": "The username and/or password was left blank.",
"errorConnection": "Couldn't connect to jfa-go.",
"errorUnknown": "Unknown error.",
"error401Unauthorized": "Unauthorised. Try refreshing the page.",
"errorSaveSettings": "Couldn't save settings."
},
"quantityStrings": {}
}

65
lang/common/en-us.json Normal file
View File

@@ -0,0 +1,65 @@
{
"meta": {
"name": "English (US)"
},
"strings": {
"username": "Username",
"password": "Password",
"emailAddress": "Email Address",
"name": "Name",
"submit": "Submit",
"send": "Send",
"success": "Success",
"continue": "Continue",
"error": "Error",
"copy": "Copy",
"copied": "Copied",
"time24h": "24h Time",
"time12h": "12h Time",
"linkTelegram": "Link Telegram",
"contactEmail": "Contact through Email",
"contactTelegram": "Contact through Telegram",
"linkDiscord": "Link Discord",
"linkMatrix": "Link Matrix",
"contactDiscord": "Contact through Discord",
"theme": "Theme",
"refresh": "Refresh",
"required": "Required",
"login": "Login",
"logout": "Logout",
"admin": "Admin",
"enabled": "Enabled",
"disabled": "Disabled",
"reEnable": "Re-enable",
"disable": "Disable",
"contactMethods": "Contact Methods",
"accountStatus": "Account Status",
"notSet": "Not set",
"expiry": "Expiry",
"add": "Add",
"edit": "Edit",
"delete": "Delete",
"myAccount": "My Account"
},
"notifications": {
"errorLoginBlank": "The username and/or password were left blank.",
"errorConnection": "Couldn't connect to jfa-go.",
"errorUnknown": "Unknown error.",
"error401Unauthorized": "Unauthorized. Try refreshing the page.",
"errorSaveSettings": "Couldn't save settings."
},
"quantityStrings": {
"year": {
"singular": "{n} Year",
"plural": "{n} Years"
},
"month": {
"singular": "{n} Month",
"plural": "{n} Months"
},
"day": {
"singular": "{n} Day",
"plural": "{n} Days"
}
}
}

48
lang/common/es-es.json Normal file
View File

@@ -0,0 +1,48 @@
{
"meta": {
"name": "Español (ES)"
},
"strings": {
"username": "Nombre de usuario",
"password": "Contraseña",
"emailAddress": "Correo electrónico",
"name": "Nombre",
"submit": "Enviar",
"send": "Enviar",
"success": "Éxito",
"continue": "Continuar",
"error": "Error",
"copy": "Copiar",
"copied": "Copiado",
"time24h": "Formato de 24 horas",
"time12h": "Formato de 12 horas",
"linkTelegram": "Enlace Telegram",
"contactEmail": "Contactar por correo electrónico",
"contactTelegram": "Contactar por Telegram",
"linkDiscord": "Enlace Discord",
"linkMatrix": "Enlace Matrix",
"contactDiscord": "Contactar por Discord",
"theme": "Tema",
"refresh": "Refrescar",
"required": "Requerido",
"login": "Acceso",
"logout": "Cerrar sesión",
"admin": "Administrador",
"enabled": "Activado",
"disabled": "Desactivado",
"reEnable": "Reactivar",
"disable": "Desactivar",
"expiry": "Expiración",
"add": "Agregar",
"edit": "Editar",
"delete": "Eliminar"
},
"notifications": {
"errorLoginBlank": "El nombre de usuario y/o la contraseña se dejaron en blanco.",
"errorConnection": "No se pudo conectar a jfa-go.",
"errorUnknown": "Error desconocido.",
"error401Unauthorized": "No autorizado. Intente actualizar la página.",
"errorSaveSettings": "No se pudo guardar la configuración."
},
"quantityStrings": {}
}

29
lang/common/fa-ir.json Normal file
View File

@@ -0,0 +1,29 @@
{
"meta": {
"name": "انگلیسی"
},
"strings": {
"username": "نام کاربری",
"password": "کلمه عبور",
"emailAddress": "پست الکترونیک",
"name": "نام",
"submit": "تایید",
"send": "ارسال",
"success": "موفقیت",
"continue": "ادامه دادن",
"error": "خطا",
"copy": "کپی",
"copied": "کپی شد",
"time24h": "ساعت 24 ساعت",
"time12h": "ساعت 12 ساعت",
"linkTelegram": "لینک تلگرام",
"contactEmail": "تماس از طریق ایمیل",
"contactTelegram": "تماس از طریق تلگرام",
"linkDiscord": "پیوند دیسکورد",
"linkMatrix": "پیوند ماتریکس",
"contactDiscord": "از طریق دیسکورد تماس بگیرید",
"theme": "موضوع"
},
"notifications": {},
"quantityStrings": {}
}

48
lang/common/fr-fr.json Normal file
View File

@@ -0,0 +1,48 @@
{
"meta": {
"name": "Français (FR)"
},
"strings": {
"username": "Nom d'utilisateur",
"password": "Mot de passe",
"emailAddress": "Adresse e-mail",
"name": "Nom",
"submit": "Soumettre",
"send": "Envoyer",
"success": "Succès",
"continue": "Continuer",
"error": "Erreur",
"copy": "Copier",
"copied": "Copié",
"time24h": "Temps 24h",
"time12h": "Temps 12h",
"linkTelegram": "Lien Telegram",
"contactEmail": "Contact par e-mail",
"contactTelegram": "Contact par Telegram",
"linkDiscord": "Lier Discord",
"linkMatrix": "Lier Matrix",
"contactDiscord": "Contacter par Discord",
"theme": "Thème",
"refresh": "Actualiser",
"required": "Requis",
"login": "S'identifier",
"logout": "Se déconnecter",
"admin": "Administrateur",
"enabled": "Activé",
"disabled": "Désactivé",
"reEnable": "Ré-activé",
"disable": "Désactivé",
"expiry": "Expiration",
"add": "Ajouter",
"edit": "Éditer",
"delete": "Effacer"
},
"notifications": {
"errorLoginBlank": "Le nom d'utilisateur et/ou le mot de passe sont vides.",
"errorConnection": "Impossible de se connecter à jfa-go.",
"errorUnknown": "Erreur inconnue.",
"error401Unauthorized": "Non autorisé. Essayez d'actualiser la page.",
"errorSaveSettings": "Impossible d'enregistrer les paramètres."
},
"quantityStrings": {}
}

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