mirror of
				https://github.com/hrfee/jellyfin-accounts.git
				synced 2025-10-31 10:09:34 +00:00 
			
		
		
		
	Added per-invite notifications for expiry and user creation
Notifications must be enabled in settings; they can then be toggled in the dropdown menu of each invite.
This commit is contained in:
		
							parent
							
								
									e80b233af2
								
							
						
					
					
						commit
						b8fdb64f68
					
				| @ -290,13 +290,6 @@ def main(): | ||||
|                 success = True | ||||
| 
 | ||||
|     else: | ||||
| 
 | ||||
|         def signal_handler(sig, frame): | ||||
|             print("Quitting...") | ||||
|             sys.exit(0) | ||||
| 
 | ||||
|         signal.signal(signal.SIGINT, signal_handler) | ||||
|         signal.signal(signal.SIGTERM, signal_handler) | ||||
|         global app | ||||
|         app = Flask(__name__, root_path=str(local_dir)) | ||||
|         app.config["DEBUG"] = config.getboolean("ui", "debug") | ||||
| @ -306,6 +299,13 @@ def main(): | ||||
|         from waitress import serve | ||||
| 
 | ||||
|         if first_run: | ||||
| 
 | ||||
|             def signal_handler(sig, frame): | ||||
|                 print("Quitting...") | ||||
|                 sys.exit(0) | ||||
| 
 | ||||
|             signal.signal(signal.SIGINT, signal_handler) | ||||
|             signal.signal(signal.SIGTERM, signal_handler) | ||||
|             import jellyfin_accounts.setup | ||||
| 
 | ||||
|             host = config["ui"]["host"] | ||||
| @ -315,6 +315,7 @@ def main(): | ||||
|         else: | ||||
|             import jellyfin_accounts.web_api | ||||
|             import jellyfin_accounts.web | ||||
|             import jellyfin_accounts.invite_daemon | ||||
| 
 | ||||
|             host = config["ui"]["host"] | ||||
|             port = config["ui"]["port"] | ||||
| @ -330,4 +331,12 @@ def main(): | ||||
|                 log.info("Starting email thread") | ||||
|                 pwr.start() | ||||
| 
 | ||||
|             def signal_handler(sig, frame): | ||||
|                 print("Quitting...") | ||||
|                 if config.getboolean("notifications", "enabled"): | ||||
|                     jellyfin_accounts.invite_daemon.inviteDaemon.stop() | ||||
|                 sys.exit(0) | ||||
| 
 | ||||
|             signal.signal(signal.SIGINT, signal_handler) | ||||
|             signal.signal(signal.SIGTERM, signal_handler) | ||||
|             serve(app, host=host, port=int(port)) | ||||
|  | ||||
| @ -16,6 +16,7 @@ class Config: | ||||
| 
 | ||||
|     @staticmethod | ||||
|     def load_config(config_path, data_dir, local_dir, log): | ||||
|         # Lord forgive me for this mess | ||||
|         config = configparser.RawConfigParser() | ||||
|         config.read(config_path) | ||||
|         for key in config["files"]: | ||||
| @ -64,6 +65,31 @@ class Config: | ||||
|             config["jellyfin"]["public_server"] = config["jellyfin"]["server"] | ||||
|         if "bs5" not in config["ui"] or config["ui"]["bs5"] == "": | ||||
|             config["ui"]["bs5"] = "false" | ||||
|         if ( | ||||
|             "expiry_html" not in config["notifications"] | ||||
|             or config["notifications"]["expiry_html"] == "" | ||||
|         ): | ||||
|             log.debug("Using default expiry notification HTML template") | ||||
|             config["notifications"]["expiry_html"] = str(local_dir / "expired.html") | ||||
|         if ( | ||||
|             "expiry_text" not in config["notifications"] | ||||
|             or config["notifications"]["expiry_text"] == "" | ||||
|         ): | ||||
|             log.debug("Using default expiry notification plaintext template") | ||||
|             config["notifications"]["expiry_text"] = str(local_dir / "expired.txt") | ||||
|         if ( | ||||
|             "created_html" not in config["notifications"] | ||||
|             or config["notifications"]["created_html"] == "" | ||||
|         ): | ||||
|             log.debug("Using default user creation notification HTML template") | ||||
|             config["notifications"]["created_html"] = str(local_dir / "created.html") | ||||
|         if ( | ||||
|             "created_text" not in config["notifications"] | ||||
|             or config["notifications"]["created_text"] == "" | ||||
|         ): | ||||
|             log.debug("Using default user creation notification plaintext template") | ||||
|             config["notifications"]["created_text"] = str(local_dir / "created.txt") | ||||
| 
 | ||||
|         return config | ||||
| 
 | ||||
|     def __init__(self, file, instance, data_dir, local_dir, log): | ||||
|  | ||||
| @ -133,6 +133,15 @@ | ||||
|             "value": "your password", | ||||
|             "description": "Password for admin page (Leave blank if using jellyfin_login)" | ||||
|         }, | ||||
|         "email": { | ||||
|             "name": "Admin email address", | ||||
|             "required": false, | ||||
|             "requires_restart": false, | ||||
|             "depends_false": "jellyfin_login", | ||||
|             "type": "text", | ||||
|             "value": "example@example.com", | ||||
|             "description": "Address to send notifications to (Leave blank if using jellyfin_login)" | ||||
|         }, | ||||
|         "debug": { | ||||
|             "name": "Debug logging", | ||||
|             "required": false, | ||||
| @ -391,6 +400,56 @@ | ||||
|             "description": "Base URL for jf-accounts. This is necessary because using a reverse proxy means the program has no way of knowing the URL itself." | ||||
|         } | ||||
|     }, | ||||
|     "notifications": { | ||||
|         "meta": { | ||||
|             "name": "Notifications", | ||||
|             "description": "Notification related settings." | ||||
|         }, | ||||
|         "enabled": { | ||||
|             "name": "Enabled", | ||||
|             "required": "false", | ||||
|             "requires_restart": true, | ||||
|             "type": "bool", | ||||
|             "value": true, | ||||
|             "description": "Enabling adds optional toggles to invites to notify on expiry and user creation." | ||||
|         }, | ||||
|         "expiry_html": { | ||||
|             "name": "Expiry email (HTML)", | ||||
|             "required": false, | ||||
|             "requires_restart": false, | ||||
|             "depends_true": "enabled", | ||||
|             "type": "text", | ||||
|             "value": "", | ||||
|             "description": "Path to expiry notification email HTML." | ||||
|         }, | ||||
|         "expiry_text": { | ||||
|             "name": "Expiry email (Plaintext)", | ||||
|             "required": false, | ||||
|             "requires_restart": "false", | ||||
|             "depends_true": "enabled", | ||||
|             "type": "text", | ||||
|             "value": "", | ||||
|             "description": "Path to expiry notification email in plaintext." | ||||
|         }, | ||||
|         "created_html": { | ||||
|             "name": "User created email (HTML)", | ||||
|             "required": false, | ||||
|             "requires_restart": false, | ||||
|             "depends_true": "enabled", | ||||
|             "type": "text", | ||||
|             "value": "", | ||||
|             "description": "Path to user creation notification email HTML." | ||||
|         }, | ||||
|         "created_text": { | ||||
|             "name": "User created email (Plaintext)", | ||||
|             "required": false, | ||||
|             "requires_restart": false, | ||||
|             "depends_true": "enabled", | ||||
|             "type": "text", | ||||
|             "value": "", | ||||
|             "description": "Path to user creation notification email in plaintext." | ||||
|         } | ||||
|     }, | ||||
|     "mailgun": { | ||||
|         "meta": { | ||||
|             "name": "Mailgun (Email)", | ||||
|  | ||||
							
								
								
									
										7
									
								
								jellyfin_accounts/data/created.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								jellyfin_accounts/data/created.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| A user was created using code {{ code }}. | ||||
| 
 | ||||
| Name: {{ username }} | ||||
| Address: {{ address }} | ||||
| Time: {{ time }} | ||||
| 
 | ||||
| Note: Notification emails can be toggled on the admin dashboard. | ||||
							
								
								
									
										10
									
								
								jellyfin_accounts/data/email.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								jellyfin_accounts/data/email.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| Hi {{ username }}, | ||||
| 
 | ||||
| Someone has recently requests a password reset on Jellyfin. | ||||
| If this was you, enter the below pin into the prompt. | ||||
| This code will expire on {{ expiry_date }}, at {{ expiry_time }} , which is in {{ expires_in }}. | ||||
| If this wasn't you, please ignore this email. | ||||
| 
 | ||||
| PIN: {{ pin }} | ||||
| 
 | ||||
| {{ message }} | ||||
							
								
								
									
										5
									
								
								jellyfin_accounts/data/expired.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								jellyfin_accounts/data/expired.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| Invite expired. | ||||
| 
 | ||||
| Code {{ code }} expired at {{ expiry }}. | ||||
| 
 | ||||
| Note: Notification emails can be toggled on the admin dashboard. | ||||
							
								
								
									
										8
									
								
								jellyfin_accounts/data/invite-email.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								jellyfin_accounts/data/invite-email.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| Hi, | ||||
| You've been invited to Jellyfin. | ||||
| To join, follow the below link. | ||||
| This invite will expire on {{ expiry_date }}, at {{ expiry_time }}, which is in {{ expires_in }}, so act quick. | ||||
| 
 | ||||
| {{ invite_link }} | ||||
| 
 | ||||
| {{ message }} | ||||
| @ -158,7 +158,7 @@ var userDefaultsModal = createModal('userDefaults'); | ||||
| var usersModal = createModal('users'); | ||||
| var restartModal = createModal('restartModal'); | ||||
| 
 | ||||
| // Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>]
 | ||||
| // Parsed invite: [<code>, <expires in _>, <1: Empty invite (no delete/link), 0: Actual invite>, <email address>, <remaining uses>, [<used-by>], <date created>, <notify on expiry>, <notify on creation>]
 | ||||
| function parseInvite(invite, empty = false) { | ||||
|     if (empty) { | ||||
|         return ["None", "", 1]; | ||||
| @ -185,6 +185,12 @@ function parseInvite(invite, empty = false) { | ||||
|     if ('created' in invite) { | ||||
|         i[6] = invite['created']; | ||||
|     } | ||||
|     if ('notify-expiry' in invite) { | ||||
|         i[7] = invite['notify-expiry']; | ||||
|     } | ||||
|     if ('notify-creation' in invite) { | ||||
|         i[8] = invite['notify-creation']; | ||||
|     } | ||||
|     return i; | ||||
| } | ||||
| 
 | ||||
| @ -292,6 +298,78 @@ function addItem(parsedInvite) { | ||||
|         dropdownLeft.appendChild(leftList); | ||||
|         dropdownContent.appendChild(dropdownLeft); | ||||
|          | ||||
|         {% if notifications %} | ||||
|         let dropdownMiddle = document.createElement('div'); | ||||
|         dropdownMiddle.id = parsedInvite[0] + '_notifyButtons'; | ||||
|         dropdownMiddle.classList.add('col'); | ||||
| 
 | ||||
|         let middleList = document.createElement('ul'); | ||||
|         middleList.classList.add('list-group', 'list-group-flush'); | ||||
|         middleList.textContent = 'Notify on:'; | ||||
| 
 | ||||
|         let notifyExpiry = document.createElement('li'); | ||||
|         notifyExpiry.classList.add('list-group-item', 'py-1', 'form-check'); | ||||
|         notifyExpiry.innerHTML = ` | ||||
|         <input class="form-check-input" type="checkbox" value="" id="${parsedInvite[0]}_notifyExpiry"> | ||||
|         <label class="form-check-label" for="${parsedInvite[0]}_notifyExpiry">Expiry</label> | ||||
|         `;
 | ||||
|         if (typeof(parsedInvite[7]) == 'boolean') { | ||||
|             notifyExpiry.getElementsByTagName('input')[0].checked = parsedInvite[7]; | ||||
|         } | ||||
| 
 | ||||
|         notifyExpiry.getElementsByTagName('input')[0].onclick = function() { | ||||
|             let req = new XMLHttpRequest(); | ||||
|             var thisEl = this; | ||||
|             let send = {}; | ||||
|             let code = thisEl.id.replace('_notifyExpiry', ''); | ||||
|             send[code] = {}; | ||||
|             send[code]['notify-expiry'] = thisEl.checked; | ||||
|             req.open("POST", "/setNotify", true); | ||||
|             req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); | ||||
|             req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); | ||||
|             req.onreadystatechange = function() { | ||||
|                 if (this.readyState == 4 && this.status != 200) { | ||||
|                     thisEl.checked = !thisEl.checked; | ||||
|                 } | ||||
|             }; | ||||
|             req.send(JSON.stringify(send)); | ||||
|         }; | ||||
|         middleList.appendChild(notifyExpiry); | ||||
| 
 | ||||
|         let notifyCreation = document.createElement('li'); | ||||
|         notifyCreation.classList.add('list-group-item', 'py-1', 'form-check'); | ||||
|         notifyCreation.innerHTML = ` | ||||
|         <input class="form-check-input" type="checkbox" value="" id="${parsedInvite[0]}_notifyCreation"> | ||||
|         <label class="form-check-label" for="${parsedInvite[0]}_notifyCreation">User creation</label> | ||||
|         `;
 | ||||
|         if (typeof(parsedInvite[8]) == 'boolean') { | ||||
|             notifyCreation.getElementsByTagName('input')[0].checked = parsedInvite[8]; | ||||
|         } | ||||
|         notifyCreation.getElementsByTagName('input')[0].onclick = function() { | ||||
|             let req = new XMLHttpRequest(); | ||||
|             var thisEl = this; | ||||
|             let send = {}; | ||||
|             let code = thisEl.id.replace('_notifyCreation', ''); | ||||
|             send[code] = {}; | ||||
|             send[code]['notify-creation'] = thisEl.checked; | ||||
|             req.open("POST", "/setNotify", true); | ||||
|             req.setRequestHeader("Authorization", "Basic " + btoa(window.token + ":")); | ||||
|             req.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); | ||||
|             req.onreadystatechange = function() { | ||||
|                 if (this.readyState == 4 && this.status != 200) { | ||||
|                     thisEl.checked = !thisEl.checked; | ||||
|                 } | ||||
|             }; | ||||
|             req.send(JSON.stringify(send)); | ||||
|         }; | ||||
|         middleList.appendChild(notifyCreation); | ||||
| 
 | ||||
|         dropdownMiddle.appendChild(middleList); | ||||
|         dropdownContent.appendChild(dropdownMiddle); | ||||
| 
 | ||||
|         {% endif %} | ||||
| 
 | ||||
| 
 | ||||
|         let dropdownRight = document.createElement('div'); | ||||
|         dropdownRight.id = parsedInvite[0] + '_usersCreated'; | ||||
|         dropdownRight.classList.add('col'); | ||||
|  | ||||
| @ -43,7 +43,10 @@ class JSONFile(dict): | ||||
|     def __delitem__(self, key): | ||||
|         data = self.readJSON(self.path) | ||||
|         super(JSONFile, self).__init__(data) | ||||
|         del data[key] | ||||
|         try: | ||||
|             del data[key] | ||||
|         except KeyError: | ||||
|             pass | ||||
|         self.writeJSON(self.path, data) | ||||
|         super(JSONFile, self).__delitem__(key) | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,15 @@ from jellyfin_accounts import config | ||||
| from jellyfin_accounts import email_log as log | ||||
| 
 | ||||
| 
 | ||||
| def format_datetime(dt): | ||||
|     result = dt.strftime(config["email"]["date_format"]) | ||||
|     if config.getboolean("email", "use_24h"): | ||||
|         result += f' {dt.strftime("%H:%M")}' | ||||
|     else: | ||||
|         result += f' {dt.strftime("%I:%M %p")}' | ||||
|     return result | ||||
| 
 | ||||
| 
 | ||||
| class Email: | ||||
|     def __init__(self, address): | ||||
|         self.address = address | ||||
| @ -75,6 +84,47 @@ class Email: | ||||
|             self.content[key] = c | ||||
|             log.info(f"{self.address}: {key} constructed") | ||||
| 
 | ||||
|     def construct_expiry(self, invite): | ||||
|         self.subject = "Notice: Invite expired" | ||||
|         log.debug(f'Constructing expiry notification for {invite["code"]}') | ||||
|         expiry = format_datetime(invite["expiry"]) | ||||
|         for key in ["text", "html"]: | ||||
|             sp = Path(config["notifications"]["expiry_" + key]) / ".." | ||||
|             sp = str(sp.resolve()) + "/" | ||||
|             template_loader = FileSystemLoader(searchpath=sp) | ||||
|             template_env = Environment(loader=template_loader) | ||||
|             fname = Path(config["notifications"]["expiry_" + key]).name | ||||
|             template = template_env.get_template(fname) | ||||
|             c = template.render(code=invite["code"], expiry=expiry) | ||||
|             self.content[key] = c | ||||
|             log.info(f"{self.address}: {key} constructed") | ||||
|         return True | ||||
| 
 | ||||
|     def construct_created(self, invite): | ||||
|         self.subject = "Notice: User created" | ||||
|         log.debug(f'Constructing expiry notification for {invite["code"]}') | ||||
|         created = format_datetime(invite["created"]) | ||||
|         if config.getboolean("email", "no_username"): | ||||
|             email = "n/a" | ||||
|         else: | ||||
|             email = invite["address"] | ||||
|         for key in ["text", "html"]: | ||||
|             sp = Path(config["notifications"]["created_" + key]) / ".." | ||||
|             sp = str(sp.resolve()) + "/" | ||||
|             template_loader = FileSystemLoader(searchpath=sp) | ||||
|             template_env = Environment(loader=template_loader) | ||||
|             fname = Path(config["notifications"]["created_" + key]).name | ||||
|             template = template_env.get_template(fname) | ||||
|             c = template.render( | ||||
|                 code=invite["code"], | ||||
|                 username=invite["username"], | ||||
|                 address=email, | ||||
|                 time=created, | ||||
|             ) | ||||
|             self.content[key] = c | ||||
|             log.info(f"{self.address}: {key} constructed") | ||||
|         return True | ||||
| 
 | ||||
|     def construct_reset(self, reset): | ||||
|         self.subject = config["password_resets"]["subject"] | ||||
|         log.debug(f"{self.address}: Using subject {self.subject}") | ||||
|  | ||||
							
								
								
									
										42
									
								
								jellyfin_accounts/invite_daemon.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								jellyfin_accounts/invite_daemon.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| from threading import Timer | ||||
| import time | ||||
| from jellyfin_accounts import config, data_store | ||||
| from jellyfin_accounts.web_api import checkInvite | ||||
| 
 | ||||
| 
 | ||||
| class Repeat: | ||||
|     def __init__(self, interval, function, *args, **kwargs): | ||||
|         self._timer = None | ||||
|         self.interval = interval | ||||
|         self.function = function | ||||
|         self.args = args | ||||
|         self.kwargs = kwargs | ||||
|         self.is_running = False | ||||
|         self.next_call = time.time() | ||||
|         self.start() | ||||
| 
 | ||||
|     def _run(self): | ||||
|         self.is_running = False | ||||
|         self.start() | ||||
|         self.function(*self.args, **self.kwargs) | ||||
| 
 | ||||
|     def start(self): | ||||
|         if not self.is_running: | ||||
|             self.next_call += self.interval | ||||
|             self._timer = Timer(self.next_call - time.time(), self._run) | ||||
|             self._timer.start() | ||||
|             self.is_running = True | ||||
| 
 | ||||
|     def stop(self): | ||||
|         self._timer.cancel() | ||||
|         self.is_running = False | ||||
| 
 | ||||
| 
 | ||||
| def checkInvites(): | ||||
|     invites = dict(data_store.invites) | ||||
|     # checkInvite already loops over everything, no point running it multiple times. | ||||
|     checkInvite(list(invites.keys())[0]) | ||||
| 
 | ||||
| 
 | ||||
| if config.getboolean("notifications", "enabled"): | ||||
|     inviteDaemon = Repeat(60, checkInvites) | ||||
| @ -106,6 +106,8 @@ def verify_password(username, password): | ||||
|             user = Account().verify_token(username, accounts) | ||||
|             if user: | ||||
|                 verified = True | ||||
|                 if user in accounts: | ||||
|                     user = accounts[user] | ||||
|             if not user: | ||||
|                 log.debug(f"User {username} not found on Jellyfin") | ||||
|                 return False | ||||
| @ -116,10 +118,10 @@ def verify_password(username, password): | ||||
|         if username == user.username and user.verify_password(password): | ||||
|             g.user = user | ||||
|             log.debug("HTTPAuth Allowed") | ||||
|             return True | ||||
|             return user | ||||
|         else: | ||||
|             log.debug("HTTPAuth Denied") | ||||
|             return False | ||||
|     g.user = user | ||||
|     log.debug("HTTPAuth Allowed") | ||||
|     return True | ||||
|     return user | ||||
|  | ||||
| @ -43,7 +43,12 @@ def static_proxy(path): | ||||
|     if "html" not in path: | ||||
|         if "admin.js" in path: | ||||
|             return ( | ||||
|                 render_template("admin.js", bsVersion=bsVersion(), css_file=css_file), | ||||
|                 render_template( | ||||
|                     "admin.js", | ||||
|                     bsVersion=bsVersion(), | ||||
|                     css_file=css_file, | ||||
|                     notifications=config.getboolean("notifications", "enabled"), | ||||
|                 ), | ||||
|                 200, | ||||
|                 {"Content-Type": "text/javascript"}, | ||||
|             ) | ||||
|  | ||||
| @ -15,6 +15,7 @@ from jellyfin_accounts import ( | ||||
|     configparser, | ||||
|     config_base_path, | ||||
| ) | ||||
| from jellyfin_accounts.email import Mailgun, Smtp | ||||
| from jellyfin_accounts import web_log as log | ||||
| from jellyfin_accounts.validate_password import PasswordValidator | ||||
| 
 | ||||
| @ -45,6 +46,22 @@ def checkInvite(code, used=False, username=None): | ||||
|             "no-limit" not in invites[invite] and invites[invite]["remaining-uses"] < 1 | ||||
|         ): | ||||
|             log.debug(f"Housekeeping: Deleting expired invite {invite}") | ||||
|             if ( | ||||
|                 config.getboolean("notifications", "enabled") | ||||
|                 and "notify" in invites[invite] | ||||
|             ): | ||||
|                 for address in invites[invite]["notify"]: | ||||
|                     if "notify-expiry" in invites[invite]["notify"][address]: | ||||
|                         if invites[invite]["notify"][address]["notify-expiry"]: | ||||
|                             method = config["email"]["method"] | ||||
|                             if method == "mailgun": | ||||
|                                 email = Mailgun(address) | ||||
|                             elif method == "smtp": | ||||
|                                 email = Smtp(address) | ||||
|                             if email.construct_expiry( | ||||
|                                 {"code": invite, "expiry": expiry} | ||||
|                             ): | ||||
|                                 email.send() | ||||
|             del data_store.invites[invite] | ||||
|         elif invite == code: | ||||
|             match = True | ||||
| @ -184,7 +201,28 @@ def newUser(): | ||||
|                 return jsonify({"error": error}) | ||||
|             except: | ||||
|                 return jsonify({"error": "Unknown error"}) | ||||
|             invites = dict(data_store.invites) | ||||
|             checkInvite(data["code"], used=True, username=data["username"]) | ||||
|             if ( | ||||
|                 config.getboolean("notifications", "enabled") | ||||
|                 and "notify" in invites[data["code"]] | ||||
|             ): | ||||
|                 for address in invites[data["code"]]["notify"]: | ||||
|                     if "notify-creation" in invites[data["code"]]["notify"][address]: | ||||
|                         if invites[data["code"]]["notify"][address]["notify-creation"]: | ||||
|                             method = config["email"]["method"] | ||||
|                             if method == "mailgun": | ||||
|                                 email = Mailgun(address) | ||||
|                             elif method == "smtp": | ||||
|                                 email = Smtp(address) | ||||
|                             if email.construct_created( | ||||
|                                 { | ||||
|                                     "code": data["code"], | ||||
|                                     "username": data["username"], | ||||
|                                     "created": datetime.datetime.now(), | ||||
|                                 } | ||||
|                             ): | ||||
|                                 email.send() | ||||
|             if user.status_code == 200: | ||||
|                 try: | ||||
|                     policy = data_store.user_template | ||||
| @ -258,6 +296,11 @@ def generateInvite(): | ||||
|         response = email.send() | ||||
|         if response is False or type(response) != bool: | ||||
|             invite["email"] = f"Failed to send to {address}" | ||||
|     if config.getboolean("notifications", "enabled"): | ||||
|         if "notify-creation" in data: | ||||
|             invite["notify-creation"] = data["notify-creation"] | ||||
|         if "notify-expiry" in data: | ||||
|             invite["notify-expiry"] = data["notify-expiry"] | ||||
|     data_store.invites[invite_code] = invite | ||||
|     log.info(f"New invite created: {invite_code}") | ||||
|     return resp() | ||||
| @ -296,6 +339,20 @@ def getInvites(): | ||||
|             invite["remaining-uses"] = 1 | ||||
|         if "email" in invites[code]: | ||||
|             invite["email"] = invites[code]["email"] | ||||
|         if "notify" in invites[code]: | ||||
|             if config.getboolean("ui", "jellyfin_login"): | ||||
|                 address = data_store.emails[g.user.id] | ||||
|             else: | ||||
|                 address = config["ui"]["email"] | ||||
|             if address in invites[code]["notify"]: | ||||
|                 if "notify-expiry" in invites[code]["notify"][address]: | ||||
|                     invite["notify-expiry"] = invites[code]["notify"][address][ | ||||
|                         "notify-expiry" | ||||
|                     ] | ||||
|                 if "notify-creation" in invites[code]["notify"][address]: | ||||
|                     invite["notify-creation"] = invites[code]["notify"][address][ | ||||
|                         "notify-creation" | ||||
|                     ] | ||||
|         response["invites"].append(invite) | ||||
|     return jsonify(response) | ||||
| 
 | ||||
| @ -407,3 +464,29 @@ def getConfig(): | ||||
|             if entry in config[section]: | ||||
|                 response_config[section][entry]["value"] = config[section][entry] | ||||
|     return jsonify(response_config), 200 | ||||
| 
 | ||||
| 
 | ||||
| @app.route("/setNotify", methods=["POST"]) | ||||
| @auth.login_required | ||||
| def setNotify(): | ||||
|     data = request.get_json() | ||||
|     change = False | ||||
|     for code in data: | ||||
|         for key in data[code]: | ||||
|             if key in ["notify-expiry", "notify-creation"]: | ||||
|                 inv = data_store.invites[code] | ||||
|                 if config.getboolean("ui", "jellyfin_login"): | ||||
|                     address = data_store.emails[g.user.id] | ||||
|                 else: | ||||
|                     address = config["ui"]["email"] | ||||
|                 if "notify" not in inv: | ||||
|                     inv["notify"] = {} | ||||
|                 if address not in inv["notify"]: | ||||
|                     inv["notify"][address] = {} | ||||
|                 inv["notify"][address][key] = data[code][key] | ||||
|                 log.debug(f"{code}: Notification settings changed") | ||||
|                 change = True | ||||
|     if change: | ||||
|         data_store.invites[code] = inv | ||||
|         return resp() | ||||
|     return resp(success=False) | ||||
|  | ||||
							
								
								
									
										47
									
								
								mail/created.mjml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								mail/created.mjml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,47 @@ | ||||
| <mjml> | ||||
|   <mj-head> | ||||
|     <mj-attributes> | ||||
|       <mj-class name="bg" background-color="#101010" /> | ||||
|       <mj-class name="bg2" background-color="#242424" /> | ||||
|       <mj-class name="text" color="rgba(255,255,255,0.8)" /> | ||||
|       <mj-class name="bold" color="rgba(255,255,255,0.87)" /> | ||||
|       <mj-class name="secondary" color="rgb(153,153,153)" /> | ||||
|       <mj-class name="blue" background-color="rgb(0,164,220)" /> | ||||
|     </mj-attributes> | ||||
|     <mj-font name="Quicksand" href="https://fonts.googleapis.com/css2?family=Quicksand" /> | ||||
|     <mj-font name="Noto Sans" href="https://fonts.googleapis.com/css2?family=Noto+Sans" /> | ||||
|   </mj-head> | ||||
|   <mj-body> | ||||
|     <mj-section mj-class="bg2"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="bold" font-size="25px" font-family="Quicksand, Noto Sans, Helvetica, Arial, sans-serif"> jellyfin-accounts </mj-text> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     <mj-section mj-class="bg"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> | ||||
|           <h3>User Created</h3> | ||||
|           <p>A user was created using code {{ code }}.</p> | ||||
|         </mj-text> | ||||
|         <mj-table mj-class="text" container-background-color="#242424"> | ||||
|           <tr style="text-align: left;"> | ||||
|             <th>Name</th> | ||||
|             <th>Address</th> | ||||
|             <th>Time</th> | ||||
|           </tr> | ||||
|           <tr style="font-style: italic; text-align: left; color: rgb(153,153,153);"> | ||||
|             <th>{{ username }}</th> | ||||
|             <th>{{ address }}</th> | ||||
|             <th>{{ time }}</th> | ||||
|         </mj-table> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     <mj-section mj-class="bg2"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="secondary" font-style="italic" font-size="14px"> | ||||
|           Notification emails can be toggled on the admin dashboard. | ||||
|         </mj-text> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     </body> | ||||
| </mjml> | ||||
							
								
								
									
										7
									
								
								mail/created.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								mail/created.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| A user was created using code {{ code }}. | ||||
| 
 | ||||
| Name: {{ username }} | ||||
| Address: {{ address }} | ||||
| Time: {{ time }} | ||||
| 
 | ||||
| Note: Notification emails can be toggled on the admin dashboard. | ||||
							
								
								
									
										36
									
								
								mail/expired.mjml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								mail/expired.mjml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | ||||
| <mjml> | ||||
|   <mj-head> | ||||
|     <mj-attributes> | ||||
|       <mj-class name="bg" background-color="#101010" /> | ||||
|       <mj-class name="bg2" background-color="#242424" /> | ||||
|       <mj-class name="text" color="rgba(255,255,255,0.8)" /> | ||||
|       <mj-class name="bold" color="rgba(255,255,255,0.87)" /> | ||||
|       <mj-class name="secondary" color="rgb(153,153,153)" /> | ||||
|       <mj-class name="blue" background-color="rgb(0,164,220)" /> | ||||
|     </mj-attributes> | ||||
|     <mj-font name="Quicksand" href="https://fonts.googleapis.com/css2?family=Quicksand" /> | ||||
|     <mj-font name="Noto Sans" href="https://fonts.googleapis.com/css2?family=Noto+Sans" /> | ||||
|   </mj-head> | ||||
|   <mj-body> | ||||
|     <mj-section mj-class="bg2"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="bold" font-size="25px" font-family="Quicksand, Noto Sans, Helvetica, Arial, sans-serif"> jellyfin-accounts </mj-text> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     <mj-section mj-class="bg"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="text" font-size="16px" font-family="Noto Sans, Helvetica, Arial, sans-serif"> | ||||
|           <h3>Invite Expired.</h3> | ||||
|           <p>Code {{ code }} expired at {{ expiry }}.</p> | ||||
|         </mj-text> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     <mj-section mj-class="bg2"> | ||||
|       <mj-column> | ||||
|         <mj-text mj-class="secondary" font-style="italic" font-size="14px"> | ||||
|           Notification emails can be toggled on the admin dashboard. | ||||
|         </mj-text> | ||||
|       </mj-column> | ||||
|     </mj-section> | ||||
|     </body> | ||||
| </mjml> | ||||
							
								
								
									
										5
									
								
								mail/expired.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								mail/expired.txt
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| Invite expired. | ||||
| 
 | ||||
| Code {{ code }} expired at {{ expiry }}. | ||||
| 
 | ||||
| Note: Notification emails can be toggled on the admin dashboard. | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user