├── LICENSE ├── README.md ├── config.py └── auto_emby_accts.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Sean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Auto Emby Accounts 2 | 3 | Use python requests to add and configure new accounts on an Emby server. 4 | 5 | ## Notice 6 | 7 | >Use this script at your own risk. 8 | 9 | ## Requirements 10 | 11 | * Python 3 12 | * requests 13 | * An Emby account with administrator privileges 14 | 15 | ## Usage 16 | 17 | To get the script started, you need a couple inputs. First, and the most important, would be the `config.py` file. 18 | 19 | ### Configuration 20 | 21 | `config.py` will have comments above the definition that will explain what each setting does. 22 | 23 | ```python 24 | # Different Logging Levels 25 | # 4: DEBUG 26 | # 3: INFO 27 | # 2: WARNING 28 | # 1: ERROR 29 | # 0: CRITICAL 30 | log_level = 3 31 | 32 | # When set true, if the script finds a user that already exists, 33 | # the script will attempt to change the policy of that user, 34 | # and add the emby connect account to that user. 35 | overwrite = False 36 | 37 | # The url to your Emby server 38 | emby_base_url = 'http://localhost:8096' 39 | 40 | # Login info for an account on your Emby server that has admin privileges 41 | # Username 42 | emby_admin_uname = '' 43 | # Password 44 | emby_admin_passwd = '' 45 | 46 | # The script will avoid doing anything to these users AND admin_uname 47 | avoid_users = [ 48 | 'Python', 49 | 'Admin1122' 50 | ] 51 | 52 | # number of seconds before the first request will timeout. 53 | timeout = 2 54 | 55 | # Determine whether or not to output in tsv format. Will be text if False 56 | tsv_out = True 57 | 58 | # If all thats provided is a connect username, this prefix will be put at 59 | # the start of the username on the server 60 | user_prefix = '!' 61 | 62 | # These are the user policy changes that will be made. Can be empty 63 | user_policy = { 64 | # 'IsAdministrator': False, # True|False 65 | # 'IsHidden': True, # True|False 66 | # 'IsHiddenRemotely': True, # True|False 67 | # 'IsDisabled': False, # True|False 68 | # 'MaxParentalRating': None, # int|None 69 | # 'BlockedTags': [], # string[] 70 | ... 71 | } 72 | ``` 73 | 74 | ### Adding Users 75 | 76 | To define the usernames of the accounts, create a file named `in.txt` and use the following format, values comma separated. 77 | 78 | ```text 79 | test, ConnectUsername1 80 | ConnectUsername2 81 | ``` 82 | 83 | With the first line in this example, the script will create a user named **test** and attempt to link **ConnectUsername1** to that account. 84 | 85 | The second line will create a user named **!ConnectUsername2** and attempt to link **ConnectUsername2** to that account. The "!" can be changed in the config under user_prefix 86 | 87 | The account password will be a random 20 character alphanumeric string. 88 | 89 | ## Output 90 | 91 | Outupt of this script, in `out.txt` or `out.tsv`, should look something like this 92 | 93 | ```tsv 94 | ---------------[2020-09-07 03:18:33.671850]--------------- 95 | LocalUser69420 EmbyConnect123 1WabMUMnaaXylCfIGrnx 96 | ``` 97 | 98 | Values are tab separated. The leftmost value is the username *on the emby server*, while the second value is the *emby connect username*. The third value is the *password* for the account on the server. 99 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | # Different Logging Levels 2 | # 4: DEBUG 3 | # 3: INFO 4 | # 2: WARNING 5 | # 1: ERROR 6 | # 0: CRITICAL 7 | log_level = 3 8 | 9 | # When set true, if the script finds a user that already exists, 10 | # the script will attempt to change the policy of that user, 11 | # and add the emby connect account to that user. 12 | overwrite = False 13 | 14 | # The url to your Emby server 15 | emby_base_url = 'http://localhost:8096' 16 | 17 | # Login info for an account on your Emby server that has admin privileges 18 | # Username 19 | emby_admin_uname = '' 20 | # Password 21 | emby_admin_passwd = '' 22 | 23 | # The script will avoid doing anything to these users AND admin_uname 24 | avoid_users = [ 25 | 'Python', 26 | 'Admin1122' 27 | ] 28 | 29 | # number of seconds before the first request will timeout. 30 | timeout = 2 31 | 32 | # Determine whether or not to output in tsv format. Will be text if False 33 | tsv_out = True 34 | 35 | # If all thats provided is a connect username, this prefix will be put at 36 | # the start of the username on the server 37 | user_prefix = '!' 38 | 39 | # These are the user policy changes that will be made. Can be empty 40 | user_policy = { 41 | 'IsAdministrator': False, # True|False 42 | 'IsHidden': True, # True|False 43 | 'IsHiddenRemotely': True, # True|False 44 | 'IsDisabled': False, # True|False 45 | # 'MaxParentalRating': None, # int|None 46 | # 'BlockedTags': [], # string[] 47 | # 'EnableUserPreferenceAccess': True, # True|False 48 | # 'AccessSchedules': [], # [Configuration.AccessSchedule{...}] 49 | # 'BlockUnratedItems': [], # string[Movie, Trailer, Series, Music, Game, Book, LiveTvChannel, LiveTvProgram, ChannelContent, Other] 50 | 'EnableRemoteControlOfOtherUsers': False, # True|False 51 | 'EnableSharedDeviceControl': True, # True|False 52 | 'EnableRemoteAccess': True, # True|False 53 | 'EnableLiveTvManagement': False, # True|False 54 | 'EnableLiveTvAccess': False, # True|False 55 | 'EnableMediaPlayback': True, # True|False 56 | 'EnableAudioPlaybackTranscoding': True, # True|False 57 | 'EnableVideoPlaybackTranscoding': True, # True|False 58 | 'EnablePlaybackRemuxing': True, # True|False 59 | 'EnableContentDeletion': False, # True|False 60 | # 'EnableContentDeletionFromFolders': [], # string[] 61 | 'EnableContentDownloading': True, # True|False 62 | 'EnableSubtitleDownloading': False, # True|False 63 | 'EnableSubtitleManagement': False, # True|False 64 | 'EnableSyncTranscoding': False, # True|False 65 | 'EnableMediaConversion': False, # True|False 66 | # 'EnabledDevices': [], # string[] 67 | 'EnableAllDevices': True, # True|False 68 | # 'EnabledChannels': [], # string[] 69 | 'EnableAllChannels': True, # True|False 70 | # 'EnabledFolders': [], # string[] 71 | 'EnableAllFolders': True, # True|False 72 | # 'InvalidLoginAttemptCount': 10, # int 73 | 'EnablePublicSharing': False, # True|False 74 | # 'BlockedMediaFolders': [], # string[] 75 | # 'BlockedChannels': [], # string[] 76 | # 'RemoteClientBitrateLimit': 12, # int 77 | # 'AuthenticationProviderId': '', # string 78 | # 'ExcludedSubFolders': [], # string[] 79 | # 'DisablePremiumFeatures': False # True|False 80 | } 81 | -------------------------------------------------------------------------------- /auto_emby_accts.py: -------------------------------------------------------------------------------- 1 | import json 2 | import logging as log 3 | import random 4 | import string 5 | import sys 6 | from datetime import datetime 7 | from os import mkdir, path 8 | 9 | import requests 10 | 11 | from config import (emby_admin_passwd, emby_admin_uname, avoid_users, 12 | emby_base_url, log_level, overwrite, timeout, tsv_out, 13 | user_policy, user_prefix) 14 | 15 | switcher = { 16 | 4: log.DEBUG, 17 | 3: log.INFO, 18 | 2: log.WARNING, 19 | 1: log.ERROR, 20 | 0: log.CRITICAL 21 | } 22 | 23 | dir = path.split(path.abspath(__file__)) 24 | dir = dir[0] 25 | log_path = f'{dir}/logs' 26 | if not path.exists(log_path): 27 | mkdir(log_path) 28 | # log setup 29 | 30 | log.basicConfig( 31 | filename=f'{dir}/logs/{path.basename(path.splitext(__file__)[0])}.log', 32 | level=switcher.get(log_level, log.DEBUG), 33 | datefmt="%Y-%m-%d %H:%M:%S", 34 | format='%(asctime)s - %(levelname)s - %(message)s' 35 | ) 36 | cons = log.StreamHandler() 37 | cons.setLevel(switcher.get(log_level, log.DEBUG)) 38 | fmt = log.Formatter('%(asctime)s - %(levelname)s - %(message)s', "%Y-%m-%d %H:%M:%S") 39 | cons.setFormatter(fmt) 40 | log.getLogger('').addHandler(cons) 41 | log.debug('Started script!') 42 | 43 | 44 | out_file = f'{dir}/out.' + 'tsv' if tsv_out else 'txt' 45 | 46 | def add_user(username, connect, 47 | passwd = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(20)), 48 | base_url = emby_base_url, 49 | admin_uname = emby_admin_uname, 50 | admin_passwd = emby_admin_passwd): 51 | ''' 52 | username: the username for the new user 53 | passwd: the password for the new user 54 | connect: the emby connect account username/email 55 | base_url: the direct url to the emby server 56 | api_token: static api token for the emby server 57 | ''' 58 | 59 | # make sure username is not the same as admin username 60 | if username == admin_uname or username in avoid_users: 61 | log.warning(f'{username} is set to be avoided! Skipping...') 62 | return 63 | 64 | log.info(f'Creating user for {username},{connect}') 65 | 66 | headers={ 67 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36", 68 | 'X-Emby-Client': "Requests Python", 69 | 'X-Emby-Device-Name': "Python", 70 | "X-Emby-Client-Version": "4.4.2.0", 71 | "X-Emby-Device-Id": "12345678-1234-1234-1234-123456789012" 72 | } 73 | 74 | # Authenticate as admin 75 | 76 | raw_data = { 77 | 'username': admin_uname, 78 | 'Pw': admin_passwd 79 | } 80 | 81 | try: 82 | res = requests.post(f'{base_url}/Users/AuthenticateByName', params=raw_data, headers=headers, timeout=timeout) 83 | except requests.exceptions.ConnectionError: 84 | log.error(f'Cannot establish connection to base_url! Stopping... base_url={base_url}') 85 | sys.exit(1) 86 | 87 | try: 88 | data = json.loads(res.text) 89 | headers.update({ 90 | 'X-Emby-Token': data['AccessToken'] 91 | }) 92 | log.info('Successfully authenticated as admin') 93 | except json.decoder.JSONDecodeError: 94 | if 'Invalid username' in res.text: 95 | log.error('Admin username or password is incorrect! Stopping...') 96 | else: 97 | log.error('Error occurred authenticating as admin. Stopping...') 98 | log.error(f'{res.status_code} - {res.text}') 99 | sys.exit(1) 100 | 101 | # 102 | # CREATE NEW USER 103 | # 104 | 105 | raw_data = { 106 | 'name': username 107 | } 108 | 109 | res = requests.post(f'{base_url}/Users/New', params=raw_data, headers=headers) 110 | new_id = '' 111 | try: 112 | data = json.loads(res.text) 113 | new_id = data['Id'] 114 | log.info(f'Created new user') 115 | 116 | except json.decoder.JSONDecodeError: 117 | if ' already exists' in res.text: 118 | if not overwrite: 119 | log.info(f'{username} already exists and overwrite is off. Skipping...') 120 | return 121 | else: 122 | # reset password if overwrite is on 123 | log.warning(f'{username} already exists. Will attempt to overwrite settings.') 124 | 125 | # get list of users 126 | res = requests.get(f'{base_url}/Users', headers=headers) 127 | try: 128 | data = json.loads(res.text) 129 | for user in data: 130 | if user['Name'] == username: 131 | new_id = user['Id'] 132 | break 133 | except json.decoder.JSONDecodeError: 134 | log.error(f'Error getting list of users! Skipping {username} {res.status_code} - {res.text}') 135 | 136 | raw_data = { 137 | 'resetPassword': True 138 | } 139 | res = requests.post(f'{base_url}/Users/{new_id}/Password', params=raw_data, headers=headers) 140 | 141 | elif 'Access token is invalid or expired.' == res.text: 142 | log.error('API token is invalid. Stopping...') 143 | sys.exit(1) 144 | else: 145 | log.error(f'Unknown error making {username}! Stopping... Status code={res.status_code} Response={res.text}') 146 | sys.exit(1) 147 | 148 | # 149 | # SET PASSWORD 150 | # 151 | 152 | if passwd == 0: 153 | passwd = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(20)) 154 | 155 | raw_data = { 156 | 'CurrentPw': '', 157 | 'NewPw': passwd 158 | } 159 | 160 | res = requests.post(f'{base_url}/Users/{new_id}/Password', params=raw_data, headers=headers) 161 | if res.status_code > 299: 162 | log.warning(f'Authenticating {username} unsuccessful. Stopping... {res.status_code} - {res.text}') 163 | sys.exit(1) 164 | else: 165 | log.info(f'Successfully set password to {passwd}') 166 | 167 | # 168 | # CHANGE POLICY 169 | # 170 | 171 | res = requests.post(f'{base_url}/Users/{new_id}/Policy', params=user_policy, headers=headers) 172 | if res.status_code > 299: 173 | log.error(f'Error occurred when changing policy! Stopping... {res.status_code} - {res.text}') 174 | sys.exit(1) 175 | log.info('Successfully set policy.') 176 | 177 | # 178 | # LINK EMBY CONNECT ACCOUNT 179 | # 180 | 181 | raw_data = { 182 | 'ConnectUsername': connect 183 | } 184 | 185 | res = requests.post(f'{base_url}/Users/{new_id}/Connect/Link', params=raw_data, headers=headers) 186 | if res.status_code > 299: 187 | log.warning(f'Connect link unsuccessful for {username}:{connect}. {res.status_code} - {res.text}') 188 | else: 189 | log.info('Successfully linked Emby connect account.') 190 | 191 | # 192 | # APPEND TO OUT.TXT 193 | # 194 | 195 | with open(out_file, 'a+', encoding='utf-8') as file: 196 | log.info(f'Writing output to {out_file}') 197 | file.seek(0) 198 | if len(file.read(10)) > 0: 199 | file.write('\n') 200 | file.write(f'{username}\t{connect}\t{passwd}') 201 | file.close() 202 | 203 | 204 | if __name__ == '__main__': 205 | with open(out_file, 'a+', encoding='utf-8') as file: 206 | file.seek(0) 207 | if len(file.read(10)) > 0: 208 | file.write('\n') 209 | file.write(f'---------------[{datetime.now()}]---------------') 210 | file.close() 211 | 212 | # Get input from in.txt 213 | in_file = f'{dir}/in.txt' 214 | if not path.isfile(in_file): 215 | log.error(f'Cant get input from {in_file}. Stopping...') 216 | sys.exit(1) 217 | 218 | new_users = [] 219 | with open(f'{dir}/in.txt', 'a+') as file: 220 | log.info(f'Getting new users from {in_file}') 221 | file.seek(0) 222 | for line in file.readlines(): 223 | new_users.append(line.strip().split(',')) 224 | file.close() 225 | for user in new_users: 226 | 227 | if len(user) == 1: 228 | add_user(f'{user_prefix}{user[0].strip()}', user[0].strip()) 229 | elif len(user) == 2: 230 | add_user(user[0].strip(), user[1].strip()) 231 | else: 232 | log.error(f'Input file is in the wrong format! Skipping line={user}') 233 | continue 234 | --------------------------------------------------------------------------------